您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關JS中promise的回調和setTimeout的回調哪個先執行,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
首先提一個小問題:運行下面這段 JS 代碼后控制臺的輸出是什么?
console.log("script start"); setTimeout(function () { console.log("setTimeout1"); }, 0); new Promise((resolve, reject) => { setTimeout(function () { console.log("setTimeout2"); resolve(); }, 100); }).then(function () { console.log("promise1"); }); Promise.resolve() .then(function () { console.log("promise2"); }) .then(function () { console.log("promise3"); }); console.log("script end");
可以先嘗試自己分析一下結果,然后再看答案:
script start
script end
promise2
promise3
setTimeout1
setTimeout2
promise1
怎么樣,你猜對了嗎?如果對這個輸出結果感到很迷惑,這篇文章或許可以幫到你。
PS:文中按照標準分析理論結果,但實際上各個瀏覽器對任務隊列的支持情況很混亂,所以如果你在瀏覽器執行代碼后發現結果不同也不必糾結;總體來說 Chrome 的支持比較好。
如果對 Promise 的用法還不熟悉,可以看我的上一篇博客:前端 | JS Promise:axios 請求結果后面的 .then() 是什么意思?
JavaScript 設計的本質是單線程語言,但隨著硬件性能的飛速發展,純單線程已經不太能夠滿足需求了。因此 JS 逐漸發展出了任務和微任務,來模擬實現多線程。
瀏覽器中,對于每個網頁(有時也可能是多個同源網頁),網頁的代碼和瀏覽器自身的用戶界面程序運共享同一個主線程,它除了運行瀏覽器交給它的 JS 代碼,也負責收集和派發事件、渲染和繪制網頁內容等等。因此,如果主線程中的某個任務阻塞了,其他任務都會受到影響;這就是為什么有時候網頁代碼出現了錯誤會導致整個網頁渲染失敗。
每個主線程都由一個事件循環 Event loops 驅動。事件循環可以理解為一個任務隊列,JS 引擎不斷的進行“循環-等待”,按順序處理隊列中的任務。事件循環中的任務稱作“任務 Task”,由宿主環境(瀏覽器)創建;每個任務都是宿主計劃執行的 JavaScript 代碼,如程序初始化、解析HTML、事件觸發的回調(例如點擊網頁上的按鈕),或是由 setTimeout()
setInterval()
等 API 添加的回調函數。
JS 引擎在執行一個任務的過程中,有時會進行一些異步操作,不會立即執行,但又想在同一個任務中完成、不留到事件循環中的下一個任務里;例如常用的 promise、監控 DOM 的回調等。這時,JS 引擎會創建一個“微任務 Mircotask”,并加入當前的微任務隊列中。(有時為了區分,也把任務task稱為“宏任務”。)
事件循環、任務、微任務的示意圖如下:
一個主線程的執行過程如下:
拿出事件循環中的下一個任務
執行任務本身的 Script 代碼;期間可能會往任務隊列、微任務隊列創建添加新任務
script 執行完后,檢查微任務隊列
如果有微任務,順序執行,期間可能還會創建新的任務和微任務
如果微任務隊列為空,這個任務執行結束,回到第一步
可以看出,在一個任務中會反復檢查微任務隊列,直到沒有微任務存在了才會執行下一個任務。因此在任務腳本和微任務腳本中創建的所有微任務都會在這個任務結束前執行,同時也意味著會早于其他所有創建的任務執行(因為新建的任務都加入了任務隊列)。
明白了任務和微任務的區別,下面再來看文章開頭的例子:
console.log("script start"); setTimeout(function () { console.log("setTimeout1"); }, 0); new Promise((resolve, reject) => { setTimeout(function () { console.log("setTimeout2"); resolve(); }, 100); }).then(function () { console.log("promise1"); }); Promise.resolve() .then(function () { console.log("promise2"); }) .then(function () { console.log("promise3"); }); console.log("script end");
接下來逐步跟蹤代碼的執行過程;如果感覺文字不夠直觀,可以看這篇博客中給出的逐步執行動畫。
整個 Script 會被宿主環境傳給 JS 引擎,作為任務隊列中的一個任務;首先執行任務中的腳本代碼:
line1: console.log("script start")
是同步代碼,直接輸出
line3: 執行 setTimeout()
,在0秒后將 console.log("setTimeout1");
加入任務隊列
line8: 執行 setTimeout()
,在0.1秒后將 console.log("setTimeout2");
和 resolve()
加入任務隊列
line16: 返回一個已成功的 promise,第一個 then 回調被加入微任務隊列
line24: console.log("script end")
是同步代碼,直接輸出
任務 script 執行完畢
此時:
控制臺輸出了 script start
script end
任務隊列中(除當前任務以外)有2個任務(兩個 setTimeout()
的回調按時間先后順序排列)
微任務隊列中有1個任務(promise 的回調)
接下來檢查微任務隊列,執行隊首的微任務:
console.log("promise2")
輸出
隱式 return,相當于返回一個 Promise.resolve(undefined)
;因此 Promise 鏈中的下一個 then 回調被加入微任務隊列
微任務執行完畢
此時:
控制臺輸出了 script start
script end
promise2
任務隊列中(除當前任務以外)有2個任務(兩個 setTimeout()
的回調按時間先后順序排列)
微任務隊列中有1個任務(第二個 promise 回調)
再次檢查微任務隊列,執行隊首的微任務:
console.log("promise3")
輸出
隱式 return(但此時 Promise 鏈已經結束了,所以無事發生)
微任務執行完畢
此時:
控制臺輸出了 script start
script end
promise2
promise3
任務隊列中(除當前任務以外)有2個任務(兩個 setTimeout()
的回調按時間先后順序排列)
微任務隊列為空
檢查微任務隊列,發現沒有微任務了,當前任務結束;開始執行任務隊列中的下一個任務(0秒后執行的回調):
console.log("setTimeout1");
輸出
任務 script 執行完畢
此時:
控制臺輸出了 script start
script end
promise2
promise3
setTimeout1
任務隊列中(除當前任務以外)有1個任務
微任務隊列為空
檢查微任務隊列,發現沒有微任務,當前任務結束;開始執行任務隊列中的下一個任務(0.1秒后執行的回調):
console.log("setTimeout2");
輸出
resolve();
將 promise 的狀態更改為已成功;then 回調被加入微任務隊列
任務 script 執行完畢
此時:
控制臺輸出了 script start
script end
promise2
promise3
setTimeout1
setTimeout2
任務隊列中只有當前任務
微任務隊列中有一個任務(promise 的回調)
檢查微任務隊列,執行隊首的微任務:
console.log("promise1")
輸出
隱式 return(但此時 Promise 鏈已經結束了,所以無事發生)
微任務執行完畢
此時:
控制臺輸出了 script start
script end
promise2
promise3
setTimeout1
setTimeout2
promise1
任務隊列中只有當前任務
微任務隊列為空
檢查微任務隊列,發現沒有微任務,當前任務結束。任務隊列中沒有其他任務,執行完畢。
關于“JS中promise的回調和setTimeout的回調哪個先執行”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。