1. JavaScript 可視化:Promise執(zhí)行徹底搞懂

        共 4904字,需瀏覽 10分鐘

         ·

        2024-04-29 09:10

             
        深入探討了 JavaScript 中 Promise 的內(nèi)部機(jī)制,解釋了它們?nèi)绾问巩惒饺蝿?wù)以非阻塞方式執(zhí)行,并展示了 Promise 的創(chuàng)建、狀態(tài)變化以及與事件循環(huán)的關(guān)系。


        正文從這開始~~

        JavaScript 中的 Promise 一開始可能會(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,并增加了額外的功能來 resolve 或 reject promise。這些功能可控制 promise 的最終 [[PromiseState]] 和 [[PromiseResult]] ,并啟動(dòng)異步任務(wù)。

        我們可以通過調(diào)用 resolve 來解決這個(gè) Promise,這是通過執(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í)的過程類似,現(xiàn)在 [[PromiseState]] 被設(shè)置為 “已拒絕”(rejected),并且 [[PromiseResult]] 被設(shè)置為我們傳遞給 reject 的值,這是 “失?。 保‵ail!)。

        當(dāng)然很好。但是,使用函數(shù)來改變對(duì)象內(nèi)部屬性有什么特別的呢?

        答案就在與我們目前跳過的兩個(gè)內(nèi)部槽相關(guān)的行為中:[[PromiseFulfillReactions]] 和 [[PromiseRejectReactions]]。

        [[PromiseFulfillReactions]] 字段包含 Promise Reactions。這是一個(gè)通過將 then 處理程序鏈接到 Promise 而創(chuàng)建的對(duì)象。

        此 Promise Reaction 包含一個(gè) [[Handler]] 屬性,其中包含我們傳遞給它的回調(diào)。當(dāng) promise resolve 時(shí),該處理程序會(huì)被添加到微任務(wù)隊(duì)列中,并可訪問 promise 解析時(shí)的值。

        當(dāng) promise 解析時(shí),這個(gè)處理程序接收到 [[PromiseResult]] 的值作為其參數(shù),然后將其推送到 Microtask Queue 微任務(wù)隊(duì)列。

        這就是 promise 的異步部分發(fā)揮作用的地方!

        微任務(wù)隊(duì)列是事件循環(huán)(event loop)中的一個(gè)專門隊(duì)列。當(dāng)調(diào)用棧(Call Stack)為空時(shí),事件循環(huán)首先處理微任務(wù)隊(duì)列中等待的任務(wù),然后再處理來自常規(guī)任務(wù)隊(duì)列(也稱為 “回調(diào)隊(duì)列” 或 “宏任務(wù)隊(duì)列”)的任務(wù)。

        如果你想了解更多,可以查看我的事件循環(huán)視頻!

        類似地,我們可以通過鏈?zhǔn)?catch 來創(chuàng)建一個(gè) Promise Reaction 記錄來處理 Promise Reject。當(dāng) Promise 被拒絕時(shí),這個(gè)回調(diào)會(huì)被添加到微任務(wù)隊(duì)列。

        到目前為止,我們只是在執(zhí)行函數(shù)內(nèi)直接調(diào)用 resolve 或 reject。雖然這是可能的,但它并沒有充分利用 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ù)在未來某個(gè)未知的時(shí)間點(diǎn)完成時(shí),我們可以使用此類異步操作通常提供的回調(diào)功能,要么使用異步任務(wù)返回的數(shù)據(jù)進(jìn)行 resolve,要么在發(fā)生錯(cuò)誤時(shí)進(jìn)行 reject。

        為了直觀地說明這一點(diǎn),讓我們一步步來執(zhí)行。為了讓這個(gè)演示簡(jiǎn)單但仍然真實(shí),我們將使用 setTimeout 來添加一些異步行為。

         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 無關(guān)。我在這里展示這個(gè)是為了展示承諾的常見用法 —— 在一些延遲后解決一個(gè) promise。

        然而,延遲本身并不是由 promise 引起的。promise 被設(shè)計(jì)為與異步操作一起工作,但這些異步操作可以來自不同的來源,如定時(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 毫秒過后,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é)果被記錄下來。由于我們沒有顯式地返回一個(gè)值,所以最后一個(gè) then promise 的 [[PromiseResult]] 是未定義的,這意味著它隱式地返回了未定義的值。

        當(dāng)然,使用數(shù)字并不是最現(xiàn)實(shí)的場(chǎng)景。相反,您可能希望逐步改變 promise 的結(jié)果,就像逐步改變圖片的外觀一樣。

        例如,您可能希望采取一系列增量的步驟,通過操作(如調(diào)整大小、應(yīng)用濾鏡、添加水印等)來改變圖像的外觀。

         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))

        這類型的任務(wù)通常涉及異步操作,這使得 promise 成為以非阻塞方式管理這些操作的良好選擇。

        結(jié)論

        長(zhǎng)話短說,Promise 只是具有一些額外功能來改變其內(nèi)部狀態(tài)的對(duì)象。

        Promises 最酷的地方在于,如果通過 then 或 catch 附加了處理程序,就可以觸發(fā)異步操作。由于處理程序被推送到微任務(wù)隊(duì)列,因此可以以非阻塞的方式處理最終結(jié)果。這樣就能更輕松地處理錯(cuò)誤、將多個(gè)操作連鎖在一起,并使代碼更具可讀性和可維護(hù)性!

        Promise 然是一個(gè)基礎(chǔ)概念,對(duì)每個(gè) JavaScript 開發(fā)人員來說都很重要。如果您有興趣了解更多,async/await 語(yǔ)法(承諾的語(yǔ)法糖)等其他特性以及 Async Generators(異步生成器)等特性將為異步代碼的使用提供更多方法。

        關(guān)于本文
        譯者:@飄飄
        作者:@Lydia Hallie
        原文:https://www.lydiahallie.com/blog/promise-execution

        瀏覽 185
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 男人午夜小视频 | 蜜臀久久99精品久久久久久果冻 | 欧美综合自拍 | 国产成人自拍视频在线 | 人人看人人摸人人操 |