W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
promise 的處理程序 ?.then
?、?.catch
? 和 ?.finally
? 都是異步的。
即便一個(gè) promise 立即被 resolve,.then
、.catch
和 .finally
下面 的代碼也會(huì)在這些處理程序之前被執(zhí)行。
示例代碼如下:
let promise = Promise.resolve();
promise.then(() => alert("promise done!"));
alert("code finished"); // 這個(gè) alert 先顯示
如果你運(yùn)行它,你會(huì)首先看到 code finished
,然后才是 promise done
。
這很奇怪,因?yàn)檫@個(gè) promise 肯定是一開(kāi)始就完成的。
為什么 .then
會(huì)在之后才被觸發(fā)?這是怎么回事?
異步任務(wù)需要適當(dāng)?shù)墓芾?。為此,ECMA 標(biāo)準(zhǔn)規(guī)定了一個(gè)內(nèi)部隊(duì)列 PromiseJobs
,通常被稱為“微任務(wù)隊(duì)列(microtask queue)”(V8 術(shù)語(yǔ))。
如 規(guī)范 中所述:
或者,簡(jiǎn)單地說(shuō),當(dāng)一個(gè) promise 準(zhǔn)備就緒時(shí),它的 .then/catch/finally
處理程序就會(huì)被放入隊(duì)列中:但是它們不會(huì)立即被執(zhí)行。當(dāng) JavaScript 引擎執(zhí)行完當(dāng)前的代碼,它會(huì)從隊(duì)列中獲取任務(wù)并執(zhí)行它。
這就是為什么在上面那個(gè)示例中 “code finished” 會(huì)先顯示。
promise 的處理程序總是會(huì)經(jīng)過(guò)這個(gè)內(nèi)部隊(duì)列。
如果有一個(gè)包含多個(gè) .then/catch/finally
的鏈,那么它們中的每一個(gè)都是異步執(zhí)行的。也就是說(shuō),它會(huì)首先進(jìn)入隊(duì)列,然后在當(dāng)前代碼執(zhí)行完成并且先前排隊(duì)的處理程序都完成時(shí)才會(huì)被執(zhí)行。
如果執(zhí)行順序?qū)ξ覀兒苤匾撛趺崔k?我們?cè)趺床拍茏?nbsp;code finished
在 promise done
之后出現(xiàn)呢?
很簡(jiǎn)單,只需要像下面這樣使用 .then
將其放入隊(duì)列:
Promise.resolve()
.then(() => alert("promise done!"))
.then(() => alert("code finished"));
現(xiàn)在代碼就是按照預(yù)期執(zhí)行的。
還記得 使用 promise 進(jìn)行錯(cuò)誤處理 一章中的 unhandledrejection
事件嗎?
現(xiàn)在,我們可以確切地看到 JavaScript 是如何發(fā)現(xiàn)未處理的 rejection 的。
如果一個(gè) promise 的 error 未被在微任務(wù)隊(duì)列的末尾進(jìn)行處理,則會(huì)出現(xiàn)“未處理的 rejection”。
正常來(lái)說(shuō),如果我們預(yù)期可能會(huì)發(fā)生錯(cuò)誤,我們會(huì)在 promise 鏈上添加 .catch
來(lái)處理 error:
let promise = Promise.reject(new Error("Promise Failed!"));
promise.catch(err => alert('caught'));
// 不會(huì)運(yùn)行:error 已經(jīng)被處理
window.addEventListener('unhandledrejection', event => alert(event.reason));
但是如果我們忘記添加 .catch
,那么,微任務(wù)隊(duì)列清空后,JavaScript 引擎會(huì)觸發(fā)下面這事件:
let promise = Promise.reject(new Error("Promise Failed!"));
// Promise Failed!
window.addEventListener('unhandledrejection', event => alert(event.reason));
如果我們遲一點(diǎn)再處理這個(gè) error 會(huì)怎樣?例如:
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)在,如果我們運(yùn)行上面這段代碼,我們會(huì)先看到 Promise Failed!
,然后才是 caught
。
如果我們并不了解微任務(wù)隊(duì)列,我們可能會(huì)想:“為什么 unhandledrejection
處理程序會(huì)運(yùn)行?我們已經(jīng)捕獲(catch)并處理了 error!”
但是現(xiàn)在我們知道了,當(dāng)微任務(wù)隊(duì)列中的任務(wù)都完成時(shí),才會(huì)生成 unhandledrejection
:引擎會(huì)檢查 promise,如果 promise 中的任意一個(gè)出現(xiàn) “rejected” 狀態(tài),unhandledrejection
事件就會(huì)被觸發(fā)。
在上面這個(gè)例子中,被添加到 setTimeout
中的 .catch
也會(huì)被觸發(fā)。只是會(huì)在 unhandledrejection
事件出現(xiàn)之后才會(huì)被觸發(fā),所以它并沒(méi)有改變什么(沒(méi)有發(fā)揮作用)。
Promise 處理始終是異步的,因?yàn)樗?promise 行為都會(huì)通過(guò)內(nèi)部的 “promise jobs” 隊(duì)列,也被稱為“微任務(wù)隊(duì)列”(V8 術(shù)語(yǔ))。
因此,.then/catch/finally
處理程序總是在當(dāng)前代碼完成后才會(huì)被調(diào)用。
如果我們需要確保一段代碼在 .then/catch/finally
之后被執(zhí)行,我們可以將它添加到鏈?zhǔn)秸{(diào)用的 .then
中。
在大多數(shù) JavaScript 引擎中(包括瀏覽器和 Node.js),微任務(wù)(microtask)的概念與“事件循環(huán)(event loop)”和“宏任務(wù)(macrotasks)”緊密相關(guān)。由于這些概念跟 promise 沒(méi)有直接關(guān)系,所以我們將在本教程另外一部分的 事件循環(huán):微任務(wù)和宏任務(wù) 一章中對(duì)它們進(jìn)行介紹。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: