Promise
Vật thể là dạng dữ liệu có những thuộc tính thành phần. Lớp là một cái khuôn để tạo các vật thể cho nhanh. Promise
vừa là tên của một lớp được định nghĩa sẵn trong JS, vừa là tên hay được đặt cho các vật thể được tạo ra từ lớp đó:
Vế trái của dòng này là vật thể promise
. Vế phải của dòng này là lớp Promise
. Ta hãy lần lượt xét hai vế này, xem chuyện gì xảy ra bên trong chúng.
Lớp Promise
¶
Lớp Promise
không nhận đối số là các giá trị như bình thường mà là cả một hàm. Hàm này được đặt tên là hàm thực thi (executor):
Bạn đã dành thời gian để tìm hiểu về Promise
, tức là bạn đang quan tâm làm sao để xử lý các hàm bất đồng bộ, làm sao để không phải chờ các hàm tốn thời gian để chạy chạy xong rồi mới có thể chạy các hàm không liên quan khác. Khi bạn gặp một hàm như vậy, bạn hãy cho nó vào bên trong hàm thực thi. Việc gom chúng lại thành một mối sẽ giúp bạn chỉ phải quản lý một lần. Vì hàm thực thi chứa các hàm chạy tốn thời gian, nên bản thân nó cũng sẽ tốn thời gian để chạy. Bất kỳ hàm nào gọi hàm bất đồng bộ đều sẽ trở thành hàm bất đồng bộ. Sự bất đồng bộ có tính lây lan.
Đối số của hàm thực thi, đến lượt nó, cũng là 2 hàm khác nhau, được gọi lần lượt là hàm giải quyết và hàm từ chối:
Khi bộ máy của JS đọc tới dòng này:
thì sẽ có 2 chuyện được xảy ra:
- Vật thể
promise
sẽ được tạo ra với hai thuộc tính làstate
vàresult
.state
sẽ được gán sẵn giá trị làpending
.result
thì chưa được gán giá trị nào, hay nói cách khác giá trị của nó làundefined
- Hàm thực thi sẽ được chạy
Không cần biết bên trong hàm thực thi viết những gì, rồi sẽ tới lúc hàm giải quyết hoặc hàm từ chối sẽ được gọi. Số phận của vật thể promise
sẽ được định đoạt thế nào tuỳ thuộc vào việc hàm nào được gọi trước. Nếu hàm giải quyết được gọi trước, thì thuộc tính state
của vật thể đang từ pending
sẽ được gán giá trị mới là fulfilled
. Ngược lại, nếu hàm từ chối được gọi trước, thì thuộc tính state
của nó sẽ được gán giá trị mới là rejected
. Ở cả hai trường hợp, giá trị được truyền vào trong hàm giải quyết/hàm từ chối sẽ được gán cho result
.
Đáng lẽ là bạn phải định nghĩa hàm giải quyết và hàm từ chối. Nhưng khi chúng được dùng làm đối số cho hàm được dùng làm đối số cho lớp Promise
(tức là hàm thực thi), thì JS sẽ tự động định nghĩa sẵn hai hàm này luôn, bạn không cần định nghĩa gì cả. Và thường người viết hay đặt tên cho hàm giải quyết là resolve
và hàm từ chối là reject
. Kết hợp với việc dùng cú pháp mũi tên cho hàm, bạn sẽ hay thấy người ta viết code như này:
const promise = new Promise((resolve, reject) => {
// ...
resolve(giáTrịSẽĐượcGánChoResult)
// ...
reject(giáTrịSẽĐượcGánChoResult)
});
Các tác giả của JS đã thiết kế lớp Promise
không nhận kết quả của hàm thực thi. Tức là việc bạn return
trong hàm thực thi không có tác dụng gì, ngoài trừ việc làm cho nó kết thúc sớm. Nếu nó kết thúc trước khi hàm giải quyết hoặc hàm từ chối được gọi thì vật thể promise
sẽ ở trạng thái pending
mãi mãi. Nếu nó có lỗi thì mặc định là đang gọi hàm từ chối.
Khi hàm giải quyết được gọi:
- Thuộc tính
state
đang từpending
sẽ được gán giá trị mới làfulfilled
- Giá trị được truyền vào trong hàm giải quyết sẽ được gán vào thuộc tính
result
- Hàm được dùng làm đối số thứ nhất của
then()
sẽ chạy
Ngược lại, khi hàm từ chối được gọi:
- Thuộc tính
state
đang từpending
sẽ được gán giá trị mới làrejected
- Giá trị được truyền vào trong hàm từ chối sẽ được gán vào thuộc tính
result
- Hàm được dùng làm đối số thứ hai của
then()
sẽ chạy
Vật thể promise
¶
Vật thể promise có 2 thuộc tính và 3 phương thức. 2 thuộc tính đó là state
và result
. 3 phương thức đó là then()
, catch()
và finally()
.
Khi engine của JS đọc tới dòng này:
thì một vật thể
promise
được tạo ra với hai thuộc tính là state
và result
. state
sẽ được gán sẵn giá trị là pending
. result
thì chưa được gán giá trị nào, hay nói cách khác giá trị của nó là undefined
.
Bạn không lấy được giá trị của result
một cách trực tiếp, mà phải lấy qua then()
hoặc catch()
. Ta hãy nói về then()
trước. Tương tự như hàm thực thi của lớp Promise
, khi mà cả 2 đối số của nó đều là hàm, cả 2 đối số của then()
cũng là hàm. Sự sắp xếp này là có ý đồ:
Vị trí đối số của… | …hàm thực thi | …then() |
---|---|---|
1 | Hàm giải quyết | Hàm nhận kết quả giải quyết |
2 | Hàm từ chối | Hàm nhận lý do từ chối |
Chắc bạn cũng đã đoán được cơ chế hoạt động của chúng. Khi hàm giải quyết được gọi trong hàm thực thi, hàm được dùng làm đối số thứ nhất của then()
sẽ được gọi. Giá trị được truyền vào hàm giải quyết sẽ được truyền vào hàm này. Tương tự, khi hàm từ chối được gọi trong hàm thực thi, hàm được dùng làm đối số thứ hai của then()
sẽ được gọi. Giá trị được truyền vào hàm từ chối sẽ được truyền vào hàm này.
Tóm lại
- Thứ gì được truyền vào hàm làm đối số thứ nhất của hàm thực thi, sẽ được truyền vào
result
và vào hàm làm đối số thứ nhất củathen()
- Thứ gì được truyền vào hàm làm đối số thứ hai của hàm thực thi, sẽ được truyền vào
result
và vào hàm làm đối số thứ hai củathen()
catch()
thực ra là then()
khi đối số thứ nhất là null
. Tức là, thứ gì được truyền vào hàm từ chối, sẽ được truyền vào hàm làm đối số của catch()
. Tức là có thể viết lại bảng trên như sau:
Nơi thông báo kết quả khi… | Nơi tiếp nhận kết quả khi… | |
---|---|---|
…hàm bất đồng bộ chạy thành công | Hàm giải quyết | then() |
…hàm bất đồng bộ gặp trục trặc khi chạy | Hàm từ chối | catch() |
Bảng 1 là dành cho cấp độ code, còn bảng 2 là ở cấp độ tư duy của con người.
Còn finally()
được dùng cho những công việc dùng để dọn dẹp mọi thứ sau khi chạy hàm bất đồng bộ, dù nó có được giải quyết hay không. Thường các hàm bất đồng bộ ít khi do bạn tự viết ra, mà là do các thư viện viết giùm bạn. Bạn sẽ được tài liệu của chúng hướng dẫn cụ thể hơn cách dùng finally()
này.
Hàm thực thi của lớp Promise
chứa hàm bất đồng bộ. Kết quả từ các hàm này sẽ được phát tán ra bên ngoài bằng các hàm giải quyết và từ chối. Tức là lớp Promise
là bên cung cấp dữ liệu (producer). Dữ liệu đó được tiếp nhận trong thuộc tính state
của vật thể promise
và được xử lý trong then()
và catch()
. Tức là vật thể promise
là bên tiêu thụ dữ liệu (consumer).
Bằng cách này, việc chạy các hàm tốn thời gian để chạy sẽ không trở thành kỳ đà cản mũi cho việc chạy các hàm không liên quan khác, mà không gặp phải tình trạng callback hell. Nhược điểm của việc này là, bạn vẫn chỉ được nhận giá trị của chúng ở bên trong then()
, vẫn chưa thể dùng được viết code với tư duy của việc chạy code tuần tự được. Để làm được điều này, bạn cần dùng tới await.
Tham khảo:
- Promise
- Promise - JavaScript | MDN
- Tất tần tật về Promise và async/await - Ehkoo
-
-:
-
Lớp Promise, resolve, reject:
-
Vật thể promise, then, catch:
- Hàm được dùng làm đối số đầu tiên của then sẽ chạy khi hàm resolve được gọi. Hàm được dùng làm đối số thứ hai của then sẽ chạy khi hàm reject được gọi
- Nếu có nhiều then nối với nhau là bởi vì trong promise có promise khác
- Vật thể promise có 2 thuộc tính là state và result, và 3 phương thức là then, catch, và finally
- Để lấy được giá trị state và result của promise, cần dùng các phương thức then và catch
- Kết quả của promise chỉ có thể được lấy ở trong then, và phải ở dạng hàm