Javascript 微任務(Microtask)

2023-02-17 10:53 更新

promise 的處理程序 ?.then?、?.catch? 和 ?.finally? 都是異步的。

即便一個 promise 立即被 resolve,.then、.catch 和 .finally 下面 的代碼也會在這些處理程序之前被執(zhí)行。

示例代碼如下:

let promise = Promise.resolve();

promise.then(() => alert("promise done!"));

alert("code finished"); // 這個 alert 先顯示

如果你運行它,你會首先看到 code finished,然后才是 promise done

這很奇怪,因為這個 promise 肯定是一開始就完成的。

為什么 .then 會在之后才被觸發(fā)?這是怎么回事?

微任務隊列(Microtask queue)

異步任務需要適當?shù)墓芾?。為此,ECMA 標準規(guī)定了一個內(nèi)部隊列 PromiseJobs,通常被稱為“微任務隊列(microtask queue)”(V8 術語)。

如 規(guī)范 中所述:

  • 隊列(queue)是先進先出的:首先進入隊列的任務會首先運行。
  • 只有在 JavaScript 引擎中沒有其它任務在運行時,才開始執(zhí)行任務隊列中的任務。

或者,簡單地說,當一個 promise 準備就緒時,它的 .then/catch/finally 處理程序就會被放入隊列中:但是它們不會立即被執(zhí)行。當 JavaScript 引擎執(zhí)行完當前的代碼,它會從隊列中獲取任務并執(zhí)行它。

這就是為什么在上面那個示例中 “code finished” 會先顯示。


promise 的處理程序總是會經(jīng)過這個內(nèi)部隊列。

如果有一個包含多個 .then/catch/finally 的鏈,那么它們中的每一個都是異步執(zhí)行的。也就是說,它會首先進入隊列,然后在當前代碼執(zhí)行完成并且先前排隊的處理程序都完成時才會被執(zhí)行。

如果執(zhí)行順序?qū)ξ覀兒苤匾撛趺崔k?我們怎么才能讓 code finished 在 promise done 之后出現(xiàn)呢?

很簡單,只需要像下面這樣使用 .then 將其放入隊列:

Promise.resolve()
  .then(() => alert("promise done!"))
  .then(() => alert("code finished"));

現(xiàn)在代碼就是按照預期執(zhí)行的。

未處理的 rejection

還記得 使用 promise 進行錯誤處理 一章中的 unhandledrejection 事件嗎?

現(xiàn)在,我們可以確切地看到 JavaScript 是如何發(fā)現(xiàn)未處理的 rejection 的。

如果一個 promise 的 error 未被在微任務隊列的末尾進行處理,則會出現(xiàn)“未處理的 rejection”。

正常來說,如果我們預期可能會發(fā)生錯誤,我們會在 promise 鏈上添加 .catch 來處理 error:

let promise = Promise.reject(new Error("Promise Failed!"));
promise.catch(err => alert('caught'));

// 不會運行:error 已經(jīng)被處理
window.addEventListener('unhandledrejection', event => alert(event.reason));

但是如果我們忘記添加 .catch,那么,微任務隊列清空后,JavaScript 引擎會觸發(fā)下面這事件:

let promise = Promise.reject(new Error("Promise Failed!"));

// Promise Failed!
window.addEventListener('unhandledrejection', event => alert(event.reason));

如果我們遲一點再處理這個 error 會怎樣?例如:

let promise = Promise.reject(new Error("Promise Failed!"));
setTimeout(() => promise.catch(err => alert('caught')), 1000);

// Error: Promise Failed!
window.addEventListener('unhandledrejection', event => alert(event.reason));

現(xiàn)在,如果我們運行上面這段代碼,我們會先看到 Promise Failed!,然后才是 caught。

如果我們并不了解微任務隊列,我們可能會想:“為什么 unhandledrejection 處理程序會運行?我們已經(jīng)捕獲(catch)并處理了 error!”

但是現(xiàn)在我們知道了,當微任務隊列中的任務都完成時,才會生成 unhandledrejection:引擎會檢查 promise,如果 promise 中的任意一個出現(xiàn) “rejected” 狀態(tài),unhandledrejection 事件就會被觸發(fā)。

在上面這個例子中,被添加到 setTimeout 中的 .catch 也會被觸發(fā)。只是會在 unhandledrejection 事件出現(xiàn)之后才會被觸發(fā),所以它并沒有改變什么(沒有發(fā)揮作用)。

總結

Promise 處理始終是異步的,因為所有 promise 行為都會通過內(nèi)部的 “promise jobs” 隊列,也被稱為“微任務隊列”(V8 術語)。

因此,.then/catch/finally 處理程序總是在當前代碼完成后才會被調(diào)用。

如果我們需要確保一段代碼在 .then/catch/finally 之后被執(zhí)行,我們可以將它添加到鏈式調(diào)用的 .then 中。

在大多數(shù) JavaScript 引擎中(包括瀏覽器和 Node.js),微任務(microtask)的概念與“事件循環(huán)(event loop)”和“宏任務(macrotasks)”緊密相關。由于這些概念跟 promise 沒有直接關系,所以我們將在本教程另外一部分的 事件循環(huán):微任務和宏任務 一章中對它們進行介紹。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號