Promise & microtask queue trong JavaScript
Promise
Promise là một special object được tích hợp trong JavaScript. Promise đóng vai trò là một placeholder cho dữ liệu chúng ta mong muốn nhận lại được từ hoạt động nền của web browser feature. Trong JavaScript, Promise được dùng để xử lý những vấn đề liên quan đến bất đồng bộ (asynchronous) một cách... đồng bộ (synchronous) hơn.
Một promise sẽ có một trong 3 trạng thái:
- pending: mới khởi tạo - chờ xử lý
- fulfilled: thành công hoàn thành thao tác xử lý
- rejected: thao tác xử lý thất bại hay xảy ra lỗi
Trong promise object, ta có 2 properties: value
và onFulfilled
Do chúng ta không biết khi nào dữ liệu chúng ta cần sẽ có nên ta cần then
để xử lý khi dữ liệu được trả về cho chúng ta, dữ liệu được trả về từ web browser API/ feature sẽ được lưu trong property value
. Property onFulfilled
là một array trống - ta có thể thêm vào đó bất kỳ functions, bất kỳ codes nào mà chúng ta muốn tự động kích hoạt để thực thi với sự trợ giúp của JavaScript khi property value
được điền vào.
Functions/codes nào được gắn vào property onFulfilled
promise object thông qua method then
, sẽ được tự động thực thi nếu như property value
của promise object đó được cập nhật.
Ở phần event loop, ta biết callback function sẽ được thêm vào callback queue để chờ được thực thi. Với promise
- ta đưa deffered functions
vào microtask queue.
Nói lý thuyết vậy thôi, giờ ta đi vào ví dụ, ta có chương trình sau:
Và vì chúng ta đã quen thuộc với các khái niệm như call stack, thread of execution, execution context hay setTimeout hoạt động nên mình sẽ đi nhanh hơn.
Khi chương trình được chạy, nó sẽ thực thi, lần lượt như sau:
- Khai báo 3 function: display, printHello, blockFor300ms ở global memory
- Tại thời điểm
1ms
- thực thi dòng 12 -setTimeout(printHello,0)
Timer
được set, và lúc này tại thời điểm 1ms
thì trạng thái của Timer cũng đã hoàn thành, ta có đẩy printHello
vào call stack và thực thi không? Không! Tại sao? Nhớ nguyên tắc của event loop chứ?
Callback queue chỉ được nhìn tới khi và chỉ khi:
- Global execution context đã thực thi xong hết code ở global
- Call stack trống
Lúc này chưa thoả điều kiện để xem đến callback queue, nên ta sẽ trở lại global.
- Tại thời điểm
2ms
- thực thi dòng 15
Khai báo constant futureData
với method fetch
Method fetch
này vừa chơi với JavaScript, vừa chơi với Web Browser.
- Trong JavaScript: tạo một promise object với 2 properties:
value
vàonFulfilled
- Với Web Browser: set một network request
Tại thời điểm này, 2 properties của promise object đều đang trống, trạng thái của promise object là pending
. Với web browser thì network request chưa hoàn thành ở thời điểm 2ms
, nên on completion của chúng ta là trả về dữ liệu cho futureData.value
chưa được thực thi.
- Trở về global và thực thi dòng 17 -
futureData.then(display)
Function display
được thêm vào property onFulfilled
của promise object futureData
, ở global memory.
Ta lại trở về global
- Tiếp theo, tại thời điểm
3ms
- dòng 19 - functionblockFor300ms
được thực thi
Sau khi blockFor300ms
được thực thi xong, lúc này thời gian từ lúc thực thi đang là 303ms
.
Lưu ý, nãy giờ bộ đếm thời gian vẫn hoạt động, và lúc 270ms
- ở network request, trạng thái đã trở thành hoàn thành - promise object futureData
lúc này đang ở trạng thái fulfilled
và property value
của futureData đã được cập nhật,
display cũng đã được thêm vào microtask queue.
Như ở phần lý thuyết trên, chúng ta có nói:
Functions/codes nào được gắn vào property
onFulfilled
của promise object thông qua methodthen
, sẽ được tự động thực thi nếu như propertyvalue
của promise object đó được cập nhật.
Lúc này, property value
của futureData
đã được cập nhật, function display
cũng được thêm vào microtask queue rồi. Vậy tại sao function display
vẫn chưa được thực thi?
Vì function display
đang nằm trong microtask queue. Cũng giống call back queue, microtask queue
chỉ được xem tới nếu như event loop xác nhận:
- Ở global: code đã được thực thi hết
- Ở call stack: trống
Vậy thì giữa microtask queue và callback queue thì event loop ưu tiên đến queue nào? Đáp án là microtask queue!
Chỉ khi nào microtask queue và call stack đang trống, và code ở global đã thực thi xong hết thì event loop mới ghé đến callback queue và kiểm tra có gì trong đó đang đợi để được thực thi không.
Và tại vì ở global vẫn còn code, nên microtask queue chưa được xem tới, display
chưa được thực thi, ta trở về global.
- Tại thời điểm
303ms
- Thực thi dòng 21 - in raMe first!
- Ai làm việc người ấy, event loop thì vẫn miệt mài xem call stack có trống hay không, ở global đã thực thi xong hết code chưa.
Và vì call stack đang trống, global đã hết code. Nó tiến đến kiểm tra microtask queue, tại đây event loop thấy function display
.
Tại thời điểm 304ms
: function display
được lấy ra khỏi microtask queue và được đẩy vào callstack để thực thi - in ra Hi
- Sau khi thấy microtask queue đã trống, event loop đi đến kiểm tra callback queue, và thấy function
printHello
ở đây.
Tại thời điểm 305ms
: function printHello
được lấy ra khỏi callback queue và được đẩy vào callstack và thực thi - in ra Hello
Vậy là chương trình đã thực thi xong, chúng ta cũng đã tìm hiểu về promise, và microtask queue hoạt động như thế nào.
Bài này nằm trong chuỗi bài viết về JavaScript của em/ mình khi đang học, nếu có hiểu sai hay còn thiếu xót mong các bạn, anh chị góp ý!
All rights reserved