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

        共 5244字,需瀏覽 11分鐘

         ·

        2024-04-26 09:17

             

        大廠技術(shù)  高級前端  Node進階

        點擊上方 程序員成長指北,關(guān)注公眾號

        回復1,加入高級Node交流群

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

        正文從這開始~~

        JavaScript 中的 Promise 一開始可能會讓人感到有些難以理解,但是如果我們能夠理解其內(nèi)部的工作原理,就會發(fā)現(xiàn)它們其實是非常易于掌握的。

        在這篇博客文章中,我們將深入探討 Promise 的一些內(nèi)部機制,并探索它們是如何使得 JavaScript 能夠執(zhí)行非阻塞的異步任務(wù)。

        一種創(chuàng)建 Promise 的方式是使用 new Promise 構(gòu)造函數(shù),它接收一個執(zhí)行函數(shù),該函數(shù)帶有 resolve 和 reject 參數(shù)。

         new Promise((resolve, reject) => {
        // TODO(Lydia): Some async stuff here
        });

        當 Promise 構(gòu)造函數(shù)被調(diào)用時,會發(fā)生以下幾件事情:

        • 創(chuàng)建一個 Promise 對象。這個 Promise 對象包含幾個內(nèi)部槽,包括 [[PromiseState]]、[[PromiseResult]][[PromiseIsHandled]]、[[PromiseFulfillReactions]] 和 [[PromiseRejectReactions]]

        • 創(chuàng)建一個 Promise 能力記錄。這個記錄 “封裝” 了 Promise,并增加了額外的功能來 resolve 或 reject promise。這些功能可控制 promise 的最終 [[PromiseState]] 和 [[PromiseResult]] ,并啟動異步任務(wù)。

        我們可以通過調(diào)用 resolve 來解決這個 Promise,這是通過執(zhí)行函數(shù)可以實現(xiàn)的。當我們調(diào)用 resolve 時:

        • [[PromiseState]] 被設(shè)置為 “已實現(xiàn)”(fulfilled)。

        • [[PromiseResult]] 被設(shè)置為我們傳遞給 resolve 的值,在這種情況下是 “完成!”(Done!)。

        調(diào)用 reject 時的過程類似,現(xiàn)在 [[PromiseState]] 被設(shè)置為 “已拒絕”(rejected),并且 [[PromiseResult]] 被設(shè)置為我們傳遞給 reject 的值,這是 “失??!”(Fail!)。

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

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

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

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

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

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

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

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

        類似地,我們可以通過鏈式 catch 來創(chuàng)建一個 Promise Reaction 記錄來處理 Promise Reject。當 Promise 被拒絕時,這個回調(diào)會被添加到微任務(wù)隊列。

        到目前為止,我們只是在執(zhí)行函數(shù)內(nèi)直接調(diào)用 resolve 或 reject。雖然這是可能的,但它并沒有充分利用 Promise 的全部功能(和主要目的)!

        在大多數(shù)情況下,我們希望在稍后的某個時間點(通常是異步任務(wù)完成時)進行 resolve 或 reject。

        異步任務(wù)在主線程之外執(zhí)行,例如讀取文件(如 fs.readFile)、提出網(wǎng)絡(luò)請求(如 https.get 或 XMLHttpRequest),或者像定時器(setTimeout)這樣簡單的任務(wù)。

        當這些任務(wù)在未來某個未知的時間點完成時,我們可以使用此類異步操作通常提供的回調(diào)功能,要么使用異步任務(wù)返回的數(shù)據(jù)進行 resolve,要么在發(fā)生錯誤時進行 reject。

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

         new Promise((resolve) => {
        setTimeout(() => resolve("Done!"), 100);
        }).then(result => console.log(result))

        首先,將 new Promise 構(gòu)造函數(shù)添加到調(diào)用棧,并創(chuàng)建 Promise 對象。

        然后,執(zhí)行函數(shù)被執(zhí)行。在函數(shù)體的第一行,我們調(diào)用了 setTimeout,并將其添加到調(diào)用堆棧中。

        setTimeout 負責在 Timers Web API 中調(diào)度計時器,延遲時間為 100 毫秒,之后我們傳遞給 setTimeout 的回調(diào)將被推送到任務(wù)隊列。

        這里的異步行為與 setTimeout 有關(guān),與 promise 無關(guān)。我在這里展示這個是為了展示承諾的常見用法 —— 在一些延遲后解決一個 promise。

        然而,延遲本身并不是由 promise 引起的。promise 被設(shè)計為與異步操作一起工作,但這些異步操作可以來自不同的來源,如定時器或網(wǎng)絡(luò)請求。

        在定時器和構(gòu)造函數(shù)從調(diào)用棧中彈出后,引擎遇到了 then。

        then 被添加到調(diào)用棧,并創(chuàng)建了一個 Promise Reaction 記錄,該處理程序就是我們作為回調(diào)傳遞給 then 處理程序的代碼。

        由于 [[PromiseState]] 仍然是 “掛起”(pending),這個 Promise Reaction 記錄會被添加到 [[PromiseFulfillReactions]] 列表中。

        100 毫秒過后,setTimeout 回調(diào)被推送到任務(wù)隊列。

        腳本已經(jīng)運行完畢,因此調(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ù)隊列,那里 then 處理程序的回調(diào)正在等待。

        回調(diào)現(xiàn)在被添加到調(diào)用棧,并記錄 result 的值,即 [[PromiseResult]] 的值;字符串 "Done!"。

        一旦回調(diào)執(zhí)行完畢并從調(diào)用棧中彈出,程序就完成了!

        除了創(chuàng)建一個 Promise Reaction 之外,then 還返回一個 Promise。這意味著我們可以將多個 then 鏈接在一起,例如:

         new Promise((resolve) => {
        resolve(1);
        })
        .then(result => result * 2)
        .then(result => result * 2)
        .then(result => console.log(result));

        執(zhí)行這段代碼時,在調(diào)用 Promise 構(gòu)造函數(shù)時會創(chuàng)建一個 Promise 對象。之后,每當引擎遇到 then 時,就會創(chuàng)建一個 Promise Reaction 記錄和一個 Promise Object。

        在這兩種情況下,then 的回調(diào)都將接收到的 [[PromiseResult]] 值乘以 2。then 的 [[PromiseResult]] 被設(shè)置為計算的結(jié)果,這個結(jié)果又被下一個 then 的處理程序使用。

        最終,結(jié)果被記錄下來。由于我們沒有顯式地返回一個值,所以最后一個 then promise 的 [[PromiseResult]] 是未定義的,這意味著它隱式地返回了未定義的值。

        當然,使用數(shù)字并不是最現(xiàn)實的場景。相反,您可能希望逐步改變 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é)論

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

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

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

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

        最后

        Node 社群

           


        我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學習感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學習、共建。下方加 考拉 好友回復「Node」即可。

           “分享、點贊、在看” 支持一下

        瀏覽 223
        2點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        2點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 亚洲日韩在线免费观看 | 日本人人操| 日韩精品一区二区成人 | 亚洲乱伦欧美乱伦 | 99白浆|