JavaScript 可視化:Promise執(zhí)行徹底搞懂
共 5244字,需瀏覽 11分鐘
·
2024-04-26 09:17
大廠技術(shù) 高級(jí)前端 Node進(jìn)階
點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
深入探討了 JavaScript 中 Promise 的內(nèi)部機(jī)制,解釋了它們?nèi)绾问巩惒饺蝿?wù)以非阻塞方式執(zhí)行,并展示了 Promise 的創(chuàng)建、狀態(tài)變化以及與事件循環(huán)的關(guān)系。
正文從這開(kāi)始~~
JavaScript 中的 Promise 一開(kāi)始可能會(huì)讓人感到有些難以理解,但是如果我們能夠理解其內(nèi)部的工作原理,就會(huì)發(fā)現(xiàn)它們其實(shí)是非常易于掌握的。
在這篇博客文章中,我們將深入探討 Promise 的一些內(nèi)部機(jī)制,并探索它們是如何使得 JavaScript 能夠執(zhí)行非阻塞的異步任務(wù)。
一種創(chuàng)建 Promise 的方式是使用 new Promise 構(gòu)造函數(shù),它接收一個(gè)執(zhí)行函數(shù),該函數(shù)帶有 resolve 和 reject 參數(shù)。
new Promise((resolve, reject) => {
// TODO(Lydia): Some async stuff here
});
當(dāng) Promise 構(gòu)造函數(shù)被調(diào)用時(shí),會(huì)發(fā)生以下幾件事情:
創(chuàng)建一個(gè) Promise 對(duì)象。這個(gè) Promise 對(duì)象包含幾個(gè)內(nèi)部槽,包括
[[PromiseState]]、[[PromiseResult]]、[[PromiseIsHandled]]、[[PromiseFulfillReactions]]和[[PromiseRejectReactions]]。創(chuàng)建一個(gè) Promise 能力記錄。這個(gè)記錄 “封裝” 了 Promise,并增加了額外的功能來(lái) resolve 或 reject promise。這些功能可控制 promise 的最終
[[PromiseState]]和[[PromiseResult]],并啟動(dòng)異步任務(wù)。
我們可以通過(guò)調(diào)用 resolve 來(lái)解決這個(gè) Promise,這是通過(guò)執(zhí)行函數(shù)可以實(shí)現(xiàn)的。當(dāng)我們調(diào)用 resolve 時(shí):
[[PromiseState]]被設(shè)置為 “已實(shí)現(xiàn)”(fulfilled)。[[PromiseResult]]被設(shè)置為我們傳遞給 resolve 的值,在這種情況下是 “完成!”(Done!)。
調(diào)用 reject 時(shí)的過(guò)程類(lèi)似,現(xiàn)在 [[PromiseState]] 被設(shè)置為 “已拒絕”(rejected),并且 [[PromiseResult]] 被設(shè)置為我們傳遞給 reject 的值,這是 “失敗!”(Fail!)。
當(dāng)然很好。但是,使用函數(shù)來(lái)改變對(duì)象內(nèi)部屬性有什么特別的呢?
答案就在與我們目前跳過(guò)的兩個(gè)內(nèi)部槽相關(guān)的行為中:[[PromiseFulfillReactions]] 和 [[PromiseRejectReactions]]。
[[PromiseFulfillReactions]] 字段包含 Promise Reactions。這是一個(gè)通過(guò)將 then 處理程序鏈接到 Promise 而創(chuàng)建的對(duì)象。
此 Promise Reaction 包含一個(gè) [[Handler]] 屬性,其中包含我們傳遞給它的回調(diào)。當(dāng) promise resolve 時(shí),該處理程序會(huì)被添加到微任務(wù)隊(duì)列中,并可訪問(wèn) promise 解析時(shí)的值。
當(dāng) promise 解析時(shí),這個(gè)處理程序接收到 [[PromiseResult]] 的值作為其參數(shù),然后將其推送到 Microtask Queue 微任務(wù)隊(duì)列。
這就是 promise 的異步部分發(fā)揮作用的地方!
微任務(wù)隊(duì)列是事件循環(huán)(event loop)中的一個(gè)專(zhuān)門(mén)隊(duì)列。當(dāng)調(diào)用棧(Call Stack)為空時(shí),事件循環(huán)首先處理微任務(wù)隊(duì)列中等待的任務(wù),然后再處理來(lái)自常規(guī)任務(wù)隊(duì)列(也稱(chēng)為 “回調(diào)隊(duì)列” 或 “宏任務(wù)隊(duì)列”)的任務(wù)。
如果你想了解更多,可以查看我的事件循環(huán)視頻!
類(lèi)似地,我們可以通過(guò)鏈?zhǔn)?catch 來(lái)創(chuàng)建一個(gè) Promise Reaction 記錄來(lái)處理 Promise Reject。當(dāng) Promise 被拒絕時(shí),這個(gè)回調(diào)會(huì)被添加到微任務(wù)隊(duì)列。
到目前為止,我們只是在執(zhí)行函數(shù)內(nèi)直接調(diào)用 resolve 或 reject。雖然這是可能的,但它并沒(méi)有充分利用 Promise 的全部功能(和主要目的)!
在大多數(shù)情況下,我們希望在稍后的某個(gè)時(shí)間點(diǎn)(通常是異步任務(wù)完成時(shí))進(jìn)行 resolve 或 reject。
異步任務(wù)在主線程之外執(zhí)行,例如讀取文件(如 fs.readFile)、提出網(wǎng)絡(luò)請(qǐng)求(如 https.get 或 XMLHttpRequest),或者像定時(shí)器(setTimeout)這樣簡(jiǎn)單的任務(wù)。
當(dāng)這些任務(wù)在未來(lái)某個(gè)未知的時(shí)間點(diǎn)完成時(shí),我們可以使用此類(lèi)異步操作通常提供的回調(diào)功能,要么使用異步任務(wù)返回的數(shù)據(jù)進(jìn)行 resolve,要么在發(fā)生錯(cuò)誤時(shí)進(jìn)行 reject。
為了直觀地說(shuō)明這一點(diǎn),讓我們一步步來(lái)執(zhí)行。為了讓這個(gè)演示簡(jiǎn)單但仍然真實(shí),我們將使用 setTimeout 來(lái)添加一些異步行為。
new Promise((resolve) => {
setTimeout(() => resolve("Done!"), 100);
}).then(result => console.log(result))
首先,將 new Promise 構(gòu)造函數(shù)添加到調(diào)用棧,并創(chuàng)建 Promise 對(duì)象。
然后,執(zhí)行函數(shù)被執(zhí)行。在函數(shù)體的第一行,我們調(diào)用了 setTimeout,并將其添加到調(diào)用堆棧中。
setTimeout 負(fù)責(zé)在 Timers Web API 中調(diào)度計(jì)時(shí)器,延遲時(shí)間為 100 毫秒,之后我們傳遞給 setTimeout 的回調(diào)將被推送到任務(wù)隊(duì)列。
這里的異步行為與 setTimeout 有關(guān),與 promise 無(wú)關(guān)。我在這里展示這個(gè)是為了展示承諾的常見(jiàn)用法 —— 在一些延遲后解決一個(gè) promise。
然而,延遲本身并不是由 promise 引起的。promise 被設(shè)計(jì)為與異步操作一起工作,但這些異步操作可以來(lái)自不同的來(lái)源,如定時(shí)器或網(wǎng)絡(luò)請(qǐng)求。
在定時(shí)器和構(gòu)造函數(shù)從調(diào)用棧中彈出后,引擎遇到了 then。
then 被添加到調(diào)用棧,并創(chuàng)建了一個(gè) Promise Reaction 記錄,該處理程序就是我們作為回調(diào)傳遞給 then 處理程序的代碼。
由于 [[PromiseState]] 仍然是 “掛起”(pending),這個(gè) Promise Reaction 記錄會(huì)被添加到 [[PromiseFulfillReactions]] 列表中。
100 毫秒過(guò)后,setTimeout 回調(diào)被推送到任務(wù)隊(duì)列。
腳本已經(jīng)運(yùn)行完畢,因此調(diào)用棧為空,這意味著該任務(wù)現(xiàn)在是從 Task Queue 中取出放到 Call Stack 上,它調(diào)用了 resolve。
調(diào)用 resolve 將 [[PromiseState]] 設(shè)置為 “fulfilled”,將 [[PromiseResult]] 設(shè)置為 “Done!”,并與 Promise Reaction 處理程序相關(guān)的代碼被添加到 Microtask Queue 中。
resolve 和回調(diào)從調(diào)用棧中彈出。
由于調(diào)用棧為空,事件循環(huán)首先檢查微任務(wù)隊(duì)列,那里 then 處理程序的回調(diào)正在等待。
回調(diào)現(xiàn)在被添加到調(diào)用棧,并記錄 result 的值,即 [[PromiseResult]] 的值;字符串 "Done!"。
一旦回調(diào)執(zhí)行完畢并從調(diào)用棧中彈出,程序就完成了!
除了創(chuàng)建一個(gè) Promise Reaction 之外,then 還返回一個(gè) Promise。這意味著我們可以將多個(gè) then 鏈接在一起,例如:
new Promise((resolve) => {
resolve(1);
})
.then(result => result * 2)
.then(result => result * 2)
.then(result => console.log(result));
執(zhí)行這段代碼時(shí),在調(diào)用 Promise 構(gòu)造函數(shù)時(shí)會(huì)創(chuàng)建一個(gè) Promise 對(duì)象。之后,每當(dāng)引擎遇到 then 時(shí),就會(huì)創(chuàng)建一個(gè) Promise Reaction 記錄和一個(gè) Promise Object。
在這兩種情況下,then 的回調(diào)都將接收到的 [[PromiseResult]] 值乘以 2。then 的 [[PromiseResult]] 被設(shè)置為計(jì)算的結(jié)果,這個(gè)結(jié)果又被下一個(gè) then 的處理程序使用。
最終,結(jié)果被記錄下來(lái)。由于我們沒(méi)有顯式地返回一個(gè)值,所以最后一個(gè) then promise 的 [[PromiseResult]] 是未定義的,這意味著它隱式地返回了未定義的值。
當(dāng)然,使用數(shù)字并不是最現(xiàn)實(shí)的場(chǎng)景。相反,您可能希望逐步改變 promise 的結(jié)果,就像逐步改變圖片的外觀一樣。
例如,您可能希望采取一系列增量的步驟,通過(guò)操作(如調(diào)整大小、應(yīng)用濾鏡、添加水印等)來(lái)改變圖像的外觀。
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
loadImage(src)
.then(image => resizeImage(image))
.then(image => applyGrayscaleFilter(image))
.then(image => addWatermark(image))
這類(lèi)型的任務(wù)通常涉及異步操作,這使得 promise 成為以非阻塞方式管理這些操作的良好選擇。
結(jié)論
長(zhǎng)話短說(shuō),Promise 只是具有一些額外功能來(lái)改變其內(nèi)部狀態(tài)的對(duì)象。
Promises 最酷的地方在于,如果通過(guò) then 或 catch 附加了處理程序,就可以觸發(fā)異步操作。由于處理程序被推送到微任務(wù)隊(duì)列,因此可以以非阻塞的方式處理最終結(jié)果。這樣就能更輕松地處理錯(cuò)誤、將多個(gè)操作連鎖在一起,并使代碼更具可讀性和可維護(hù)性!
Promise 然是一個(gè)基礎(chǔ)概念,對(duì)每個(gè) JavaScript 開(kāi)發(fā)人員來(lái)說(shuō)都很重要。如果您有興趣了解更多,async/await 語(yǔ)法(承諾的語(yǔ)法糖)等其他特性以及 Async Generators(異步生成器)等特性將為異步代碼的使用提供更多方法。
關(guān)于本文
譯者:@飄飄
作者:@Lydia Hallie
原文:https://www.lydiahallie.com/blog/promise-execution
最后
Node 社群
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
“分享、點(diǎn)贊、在看” 支持一下
