JavaScript中Promise的執(zhí)行順序舉例詳析
概述
理解 Promise
的執(zhí)行順序時,需要牢記以下兩點:
微任務(wù)與宏任務(wù)的優(yōu)先級:
- 微任務(wù):
Promise.then()
、catch
、finally
是微任務(wù)。 - 宏任務(wù):
setTimeout
、setInterval
是宏任務(wù)。 - 微任務(wù)的優(yōu)先級高于宏任務(wù):在一次事件循環(huán)中,先清空所有的微任務(wù)隊列,再執(zhí)行下一個宏任務(wù)。
- 微任務(wù):
Promise 是基于微任務(wù)實現(xiàn)的:
- 當(dāng)一個
Promise
的狀態(tài)變?yōu)?nbsp;resolved
或rejected
時,它的.then()
回調(diào)會被加入微任務(wù)隊列,等待當(dāng)前任務(wù)(包括微任務(wù))完成后執(zhí)行。
- 當(dāng)一個
示例代碼分析
代碼分析
以下代碼可以幫助理解 Promise
和 setTimeout
的執(zhí)行順序:
console.log("script start"); setTimeout(() => { console.log("setTimeout 1"); }, 0); Promise.resolve() .then(() => { console.log("promise 1"); return Promise.resolve().then(() => { console.log("promise 2"); }); }) .then(() => { console.log("promise 3"); }); setTimeout(() => { console.log("setTimeout 2"); }, 0); console.log("script end");
執(zhí)行過程解析
同步任務(wù):立即執(zhí)行
console.log("script start")
輸出"script start"
.setTimeout
的兩個回調(diào)函數(shù)被放入 宏任務(wù)隊列,等待事件循環(huán)調(diào)度。Promise.resolve()
被調(diào)用,then()
的回調(diào)被放入 微任務(wù)隊列。
輸出結(jié)果:
script start
script end主線程執(zhí)行完同步任務(wù)后,開始執(zhí)行微任務(wù)隊列
- 微任務(wù)隊列的順序如下:
- 第一個
.then()
輸出"promise 1"
并返回一個新的Promise
。 - 新的
Promise.then()
輸出"promise 2"
。 - 第二個
.then()
輸出"promise 3"
。
- 第一個
輸出結(jié)果:
promise 1 promise 2 promise 3
- 微任務(wù)隊列的順序如下:
清空微任務(wù)隊列后,開始執(zhí)行宏任務(wù)隊列
- 宏任務(wù)隊列的兩個
setTimeout
回調(diào)依次執(zhí)行,輸出"setTimeout 1"
和"setTimeout 2"
。
輸出結(jié)果:
setTimeout 1 setTimeout 2
- 宏任務(wù)隊列的兩個
最終輸出
綜合以上,代碼的輸出順序為:
script start script end promise 1 promise 2 promise 3 setTimeout 1 setTimeout 2
總結(jié)
- 同步任務(wù)優(yōu)先執(zhí)行,輸出
script start
和script end
。 - 微任務(wù)隊列優(yōu)先于宏任務(wù)隊列。
Promise.then()
的回調(diào)會依次進入微任務(wù)隊列。setTimeout
的回調(diào)進入宏任務(wù)隊列,最后執(zhí)行。
(拓展)問題補充
如果第一個then不是返回return promise,而是直接執(zhí)行一個Promise.resolve().then(() => { console.log("promise 2"); });
結(jié)果是不是
會變成 promise 1
→ promise 3
→ promise 2
。結(jié)果是的。
為什么會這樣?
當(dāng) Promise.resolve().then()
不通過 return
將內(nèi)部的 Promise
鏈接到外部 then
時,promise 2
的執(zhí)行不再是當(dāng)前鏈的一部分,它會被單獨添加到 微任務(wù)隊列的末尾,導(dǎo)致執(zhí)行順序的變化。
示例代碼
以下是修改后的代碼:
console.log("script start"); Promise.resolve() .then(() => { console.log("promise 1"); Promise.resolve().then(() => { console.log("promise 2"); }); }) .then(() => { console.log("promise 3"); }); console.log("script end");
執(zhí)行過程解析
同步任務(wù)
- 輸出
"script start"
。 - 主線程繼續(xù),將第一個
Promise.then()
的回調(diào)加入 微任務(wù)隊列。 - 輸出
"script end"
。
當(dāng)前輸出:
script start script end
- 輸出
微任務(wù)隊列開始執(zhí)行
- 執(zhí)行第一個
.then()
,輸出"promise 1"
。
在此回調(diào)中,一個新的微任務(wù)(promise 2
的回調(diào))被加入 微任務(wù)隊列末尾。 - 執(zhí)行第二個
.then()
的回調(diào),輸出"promise 3"
。
當(dāng)前輸出:
promise 1 promise 3
- 執(zhí)行第一個
微任務(wù)隊列剩余任務(wù)
- 微任務(wù)隊列中剩余的任務(wù)是
promise 2
的回調(diào),輸出"promise 2"
。
最終輸出:
promise 2
- 微任務(wù)隊列中剩余的任務(wù)是
總輸出結(jié)果
綜合以上,完整的輸出順序是:
script start script end promise 1 promise 3 promise 2
關(guān)鍵點解析
鏈?zhǔn)秸{(diào)用和微任務(wù)隊列當(dāng)你不通過
return
將一個新的Promise
鏈接到當(dāng)前then
,它的回調(diào)會獨立加入 微任務(wù)隊列的末尾,而不是成為當(dāng)前鏈的一部分。對比:返回 Promise如果
return Promise.resolve().then(...)
,那么promise 2
的執(zhí)行會成為當(dāng)前鏈的一部分,順序為promise 1 → promise 2 → promise 3
。
改變的核心代碼
獨立的微任務(wù):
Promise.resolve().then(() => { console.log("promise 2"); });
作為鏈的一部分:
return Promise.resolve().then(() => { console.log("promise 2"); });
兩種寫法的區(qū)別在于是否將新的 Promise
加入當(dāng)前鏈。
總結(jié)
到此這篇關(guān)于JavaScript中Promise的執(zhí)行順序舉例詳析的文章就介紹到這了,更多相關(guān)JS Promise執(zhí)行順序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
怎么限制input的text里輸入的值只能是數(shù)字(正則、js)
這篇文章主要通過正則表達式和JS代碼限制input的text里輸入的值只能是數(shù)字的相關(guān)資料,需要的朋友可以參考下2016-05-05layer實現(xiàn)登錄彈框,登錄成功后關(guān)閉彈框并調(diào)用父窗口的例子
今天小編就為大家分享一篇layer實現(xiàn)登錄彈框,登錄成功后關(guān)閉彈框并調(diào)用父窗口的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09JavaScript根據(jù)數(shù)據(jù)生成百分比圖和柱狀圖的實例代碼
這篇文章介紹了JavaScript根據(jù)數(shù)據(jù)生成百分比圖和柱狀圖的實例代碼,有需要的朋友可以參考一下2013-07-07HTML5+Canvas調(diào)用手機拍照功能實現(xiàn)圖片上傳(下)
這篇文章主要為大家詳細介紹了HTML5+Canvas調(diào)用手機拍照功能實現(xiàn)圖片上傳,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04webpack打包中path.resolve(__dirname, 'dist')的含義解
這篇文章主要介紹了webpack打包中path.resolve(__dirname, 'dist')的含義解析,path:path.resolve(__dirname, 'dist')就是在打包之后的文件夾上拼接了一個文件夾,在打包時,直接生成,本文給大家講解的非常詳細,需要的朋友可以參考下2023-05-05