本文由 dellyoung 獨家受權發佈,若是以爲文章有幫助,歡迎點擊閱讀原文給做者點個贊~前端
「 本文共 8606 字,預計閱讀全文須要 28 分鐘 」」
本文將從萬物初始講起JS世界的運轉規則,也就是事件循環,在這個過程當中你就能明白爲何須要這些規則。有了規則JS世界才能穩穩的運轉起來,因此這些規則很是重要,可是你真的瞭解它們了嗎?面試
閱讀本文前能夠思考下面幾個問題:編程
第一節:深刻理解JS的深拷貝
第二節:深刻理解JS的原型和原型鏈
第三節:深刻理解JS的事件循環json
本文基於chromium內核講解」
剛開始讓萬物運轉是件挺容易的事情,畢竟剛開始嘛,也沒什麼復瑣事,好比有以下一系列任務:api
function MainThread() { let a = 1 + 2; let b = 3 / 4; console.log(a + b) }
JS世界拿到這個任務一看很簡單啊:首先建一條流水線(一個單線程),而後依次處理這三個任務,最後執行完後撤掉流水線(線程退出)就好了。數組
如今我們的事件循環系統很容易就能處理這幾個任務了,能夠得出:promise
單線程解決了處理任務的問題:若是有一些肯定好的任務,可使用一個單線程來按照順序處理這些任務。
可是有一些問題:瀏覽器
要想解決上面的問題,就須要引入循環機制,讓線程持續運轉,再來任務就能執行啦前端框架
轉換成代碼就像這樣微信
function MainThread() { while(true){ ······ } }
如今的JS的事件循環系統就能持續運轉起來啦:
循環機制解決了不能循環執行的問題:引入了循環機制,經過一個 while 循環語句,線程會一直循環執行
不過又有其餘問題出現了:
交給主線程的這些任務,確定得按必定順序執行,而且還要得主線程空閒才能作這些任務,因此就須要先將這些任務按順序存起來,等着主線程有空後一個個執行。
可是如何按順序存儲這些任務呢?
很容易想到用隊列,由於這種狀況符合隊列「先進先出」的特色,也就是說 要添加任務的話,添加到隊列的尾部;要取出任務的話,從隊列頭部去取。
有了隊列以後,主線程就能夠從消息隊列中讀取一個任務,而後執行該任務,主線程就這樣一直循環往下執行,所以只要消息隊列中有任務,主線程就會去執行。
咱們要注意的是:
其實渲染進程會有一個IO線程:IO線程負責和其它進程IPC通訊,接收其餘進程傳進來的消息,如圖所示:
我們如今知道頁面主線程是如何接收外部任務了:
到如今,其實已經完成chromium內核基本的事件循環系統了:
如今已經知道:頁面線程全部執行的任務都來自於任務隊列。任務隊列是「先進先出」的,也就是說放入隊列中的任務,須要等待前面的任務被執行完,纔會被執行。
這就致使兩個問題了:
處理高優先級的任務-微任務
以監聽dom變化爲例,若是dom變化則觸發任務回調,可是若是將這個任務回調放到隊列尾部,等到輪到它出隊列,可能已通過去一段時間了,影響了監聽的實時性。而且若是變化很頻繁的話,往隊列中插入了這麼多的任務,必然也下降了效率。
因此須要一種既能兼顧實時性,又能兼顧效率的方法。
解決方案V8引擎已經給出了:在每一個任務內部,開闢一個屬於該任務的隊列,把須要兼顧實時性和效率的任務,先放到這個任務內部的隊列中等待執行,等到當前任務快執行完準備退出前,執行該任務內部的隊列。我們把放入到這個特殊隊列中的任務稱爲微任務。
這樣既不會影響當前的任務又不會下降多少實時性。
如圖所示以任務1放爲例:
能夠總結一下:
V8引擎一直循環執行微任務隊列中的任務,直到隊列爲空纔算執行結束。也就是說在執行微任務過程當中產生的新的微任務並不會推遲到下個宏任務中執行,而是在當前的宏任務中繼續執行。
咱們來看看微任務怎麼產生?在現代瀏覽器裏面,產生微任務只有兩種方式。
第二種方式是使用 Promise,當調用 Promise.resolve()或者 Promise.reject() 的時候,也會產生微任務。
而常見的宏任務又有哪些呢?
消息通道:MessageChannel
而且咱們要知道:
若是頁面上有動畫,當有一個JavaScript任務運行時間較長的時候(好比大於16.7ms),主線程沒法交給排版引擎 Blink來工做,動畫也就沒法渲染來,形成卡頓的效果。這固然是很是糟糕的用戶體驗。想要避免這種問題,就須要用到回調來解決。
到如今已經知道了,JS世界是由事件循環和任務隊列來驅動的。
setTimeout你們都很熟悉,它是一個定時器,用來指定某個函數在多少毫秒後執行。那瀏覽器是怎麼實現setTimeout的呢?
要搞清楚瀏覽器是怎麼實現setTimeout就先要弄明白兩個問題:
回調任務的信息包含:回調函數、當前發起時間、延遲執行時間
具體我畫了個圖:
setTimeout到時間後怎麼觸發
當主線程執行完任務隊列中的一個任務以後,主線程會對延遲任務隊列中的任務,經過當前發起時間和延遲執行時間計算出已經到期的任務,而後依次的執行這些到期的任務,等到期的任務所有執行完後,主線程就進入到下一次循環中。具體呢我也畫了個圖:
ps:爲了講清楚,畫配圖真的好累哦,點個贊吧!」
到這就清楚setTimeout是如何實現的了:
promise很是重要,新加入的原生api和前端框架都大量使用了promise,promise已然成爲前端的「水」和「電」。
promise解決了什麼問題呢?promise解決的是異步編碼風格的問題。
我們來看,之前咱們的異步代碼長這樣:
let fs = require('fs'); fs.readFile('./dellyoung.json',function(err,data){ fs.readFile(data,function(err,data){ fs.readFile(data,function(err,data){ console.log(data) }) }) })
層層嵌套,環環相扣,想拿到回調結果已經夠費勁了,若是還想進行錯誤處理。。。那簡直太難受了。
而promise出現後,這些問題迎刃而解:
let fs = require('fs'); function getFile(url){ return new Promise((resolve,reject)=>{ fs.readFile(url,function(error,data){ if(error){ reject(error) } resolve(data) }) }) } getFile('./dellyoung.json').then(data=>{ return getFile(data) }).then(data=>{ return getFile(data) }).then(data=>{ console.log(data) }).catch(err=>{ // 統一錯誤處理 console.log(err) })
簡直好用了太多。
能夠發現,使用promise解決了異步回調的嵌套調用和錯誤處理的問題。
你們已經知道promise很是重要了,可是如何徹底學會promise呢?手撕一遍promise天然就貫通啦,我們開始撕,在過程當中抽絲剝繭。
咱們如今想寫一個promise,可是誰來告訴怎樣纔算一個合格的promise,不用擔憂,業界是經過一個規則指標來實現promise的,這就是Promise / A+,還有一篇翻譯可供參考【翻譯】Promises / A+規範。
接下來就開始逐步實現吧!
先從一個最簡單的promise實現開始
先實現promise的地基:初始化用的構造函數
class ObjPromise { constructor(executor) { // promise狀態 this.status = 'pending'; // resolve回調成功,resolve方法裏的參數值 this.successVal = null; // reject回調成功,reject方法裏的參數值 this.failVal = null; // 定義resolve函數 const resolve = (successVal) => { if (this.status !== 'pending') { return; } this.status = 'resolve'; this.successVal = successVal; }; // 定義reject const reject = (failVal) => { if (this.status !== 'pending') { return; } this.status = 'reject'; this.failVal = failVal; }; try { // 將resolve函數給使用者 executor(resolve, reject) } catch (e) { // 執行拋出異常時 reject(e) } } }
我們先寫一個constructor用來初始化promise。
接下來分析一下:
then方法做用:拿到promise中的resolve或者reject的值。
1.基礎版then方法
在class裏面放上以下then方法:
then(onResolved, onRejected) { switch (this.status) { case "resolve": onResolved(this.successVal); break; case "reject": onRejected(this.failVal); break; } }
來分析一下:
new Promise((resolve,reject)=>{ resolve(1); }).then((resp)=>{ console.log(resp); // 1 }).then(()=>{ ... })
2.使then方法支持鏈式調用
其實支持鏈式核心就是then方法要返回一個新的promise,我們來改造一下實現支持鏈式調用。
then(onResolved, onRejected) { // 要返回一個promise對象 let resPromise; switch (this.status) { case "resolve": resPromise = new ObjPromise((resolve, reject) => { try{ // 傳入的第一個函數 onResolved(this.successVal); resolve(); }catch (e) { reject(e); } }); break; case "reject": resPromise = new ObjPromise((resolve, reject) => { try{ // 傳入的第二個函數 onRejected(this.failVal); resolve(); }catch (e) { reject(e); } }); break; } return resPromise; }
再分析一下:
可是你沒有發現一個問題,我then方法內的第一個參數,也就是onResolved()函數,函數內部的返回值應該是要可以傳遞給下面接着進行鏈式調用的then方法的,以下所示:
new Promise((resolve,reject)=>{ resolve(1); }).then((resp)=>{ console.log(resp); // 1 return 2; // <<< 關注這行 }).then((resp)=>{ console.log(resp); // 2 接受到了參數2 })
這該如何實現呢?
其實很簡單:
then(onResolved, onRejected) { // 定義這個變量保存要返回的promise對象 let resPromise; switch (this.status) { case "resolve": resPromise = new ObjPromise((resolve, reject) => { try{ // 傳入的第一個函數 let data = onResolved(this.successVal); resolve(data); }catch (e) { reject(e); } }); break; case "reject": resPromise = new ObjPromise((resolve, reject) => { try{ // 傳入的第二個函數 let data = onRejected(this.failVal); resolve(data); }catch (e) { reject(e); } }); break; } return resPromise; }
很簡單:
再看看這段常見的代碼:
new Promise((resolve,reject)=>{ resolve(1); }).then((resp)=>{ console.log(resp); // 1 return 2; }).then((resp)=>{ console.log(resp); // 2 })
能夠看到,then方法的參數能夠只傳一個,繼續來改造:
then(onResolved, onRejected) { const isFunction = (fn) => { return Object.prototype.toString.call(fn) === "[object Function]" }; onResolved = isFunction(onResolved) ? onResolved : (e) => e; onRejected = isFunction(onRejected) ? onRejected : err => { throw err }; ······ }
分析一下:
class ObjPromise { constructor(executor) { // promise狀態 this.status = 'pending'; // resolve回調成功,resolve方法裏的參數值 this.successVal = null; // reject回調成功,reject方法裏的參數值 this.failVal = null; // 定義resolve函數 const resolve = (successVal) => { if (this.status !== 'pending') { return; } this.status = 'resolve'; this.successVal = successVal; }; // 定義reject const reject = (failVal) => { if (this.status !== 'pending') { return; } this.status = 'reject'; this.failVal = failVal; }; try { // 將resolve函數給使用者 executor(resolve, reject) } catch (e) { // 執行拋出異常時 reject(e) } } then(onResolved, onRejected) { const isFunction = (fn) => { return Object.prototype.toString.call(fn) === "[object Function]" }; onResolved = isFunction(onResolved) ? onResolved : (e) => e; onRejected = isFunction(onRejected) ? onRejected : err => { throw err }; // 定義這個變量保存要返回的promise對象 let resPromise; switch (this.status) { case "resolve": resPromise = new ObjPromise((resolve, reject) => { try{ // 傳入的第一個函數 let data = onResolved(this.successVal); resolve(data); }catch (e) { reject(e); } }); break; case "reject": resPromise = new ObjPromise((resolve, reject) => { try{ // 傳入的第二個函數 let data = onRejected(this.failVal); resolve(data); }catch (e) { reject(e); } }); break; } return resPromise; } }
你能夠在控制檯運行下面這個測試代碼:
new ObjPromise((resolve,reject)=>{ resolve(1); }).then((resp)=>{ console.log(resp); // 1 return 2; }).then((resp)=>{ console.log(resp); // 2 })
控制檯會依次打印出 1 2。
5.then返回值處理
到如今同步promise代碼已經沒問題啦,可是還不夠,由於Promise/A+規定:then方法能夠返回任何值,固然包括Promise對象,而若是是Promise對象,咱們就須要將他拆解,直到它不是一個Promise對象,取其中的值。
由於status狀態爲'resolve'和'reject'時都須要進行這樣的處理,因此咱們就能夠把處理過程封裝成一個函數,代碼以下:
then(onResolved, onRejected) { ··· let resPromise; switch (this.status) { case "resolve": resPromise = new ObjPromise((resolve, reject) => { try { // 傳入的第一個函數 let data = onResolved(this.successVal); this.resolvePromise(data, resolve, reject); } catch (e) { reject(e); } }); break; case "reject": resPromise = new ObjPromise((resolve, reject) => { try { // 傳入的第二個函數 let data = onRejected(this.failVal); this.resolvePromise(data, resolve, reject); } catch (e) { reject(e); } }); break; } return resPromise; } // data爲返回值 // newResolve爲新的promise的resolve方法 // newReject爲新的promise的reject方法 resolvePromise(data, newResolve, newReject) { // 判斷是不是promise,不是直接resolve就行 if(!(data instanceof ObjPromise)){ return newResolve(data) } try { let then = data.then; const resolveFunction = (newData) => { this.resolvePromise(newData, newResolve, newReject); }; const rejectFunction = (err) => { newReject(err); }; then.call(data, resolveFunction, rejectFunction) } catch (e) { // 錯誤處理 newReject(e); } }
分析一下:
如今又有問題了:
若是新的promise出現循環引用的話就永遠也遞歸不到頭了
看看執行下面這個代碼:
let testPromise = new ObjPromise((resolve, reject) => { resolve(1); }) let testPromiseB = testPromise.then((resp) => { console.log(resp); // 1 return testPromiseB; })
會報錯棧溢出。
解決這個問題的方法就是:經過給resolvePromise()方法傳遞當前新的promise對象,判斷當前新的promise對象和函數執行返回值不一樣就能夠了
class ObjPromise { constructor(executor) { // promise狀態 this.status = 'pending'; // resolve回調成功,resolve方法裏的參數值 this.successVal = null; // reject回調成功,reject方法裏的參數值 this.failVal = null; // 定義resolve函數 const resolve = (successVal) => { if (this.status !== 'pending') { return; } this.status = 'resolve'; this.successVal = successVal; }; // 定義reject const reject = (failVal) => { if (this.status !== 'pending') { return; } this.status = 'reject'; this.failVal = failVal; }; try { // 將resolve函數給使用者 executor(resolve, reject) } catch (e) { // 執行拋出異常時 reject(e) } } resolvePromise(resPromise, data, newResolve, newReject) { if (resPromise === data) { return newReject(new TypeError('循環引用')) } if (!(data instanceof ObjPromise)) { return newResolve(data) } try { let then = data.then; const resolveFunction = (newData) => { this.resolvePromise(resPromise, newData, newResolve, newReject); }; const rejectFunction = (err) => { newReject(err); }; then.call(data, resolveFunction, rejectFunction) } catch (e) { // 錯誤處理 newReject(e); } } then(onResolved, onRejected) { const isFunction = (fn) => { return Object.prototype.toString.call(fn) === "[object Function]" }; onResolved = isFunction(onResolved) ? onResolved : (e) => e; onRejected = isFunction(onRejected) ? onRejected : err => { throw err }; // 定義這個變量保存要返回的promise對象 let resPromise; switch (this.status) { case "resolve": resPromise = new ObjPromise((resolve, reject) => { try { // 傳入的第一個函數 let data = onResolved(this.successVal); this.resolvePromise(resPromise, data, resolve, reject); } catch (e) { reject(e); } }); break; case "reject": resPromise = new ObjPromise((resolve, reject) => { try { // 傳入的第二個函數 let data = onRejected(this.failVal); this.resolvePromise(resPromise, data, resolve, reject); } catch (e) { reject(e); } }); break; } return resPromise; } }
能夠在控制檯中調用以下代碼試試啦:
new ObjPromise((resolve, reject) => { resolve(1); }).then((resp) => { console.log(resp); // 1 return 2 }).then((resp) => { console.log(resp); // 2 return new ObjPromise((resolve, reject) => { resolve(3) }) }).then((resp) => { console.log(resp); // 3 });
控制檯會一次打印出 1 2 3
如今我們實現了同步版的promise,可是不少狀況下,promise的resolve或reject是被異步調用的,異步調用的話,執行到then()方法時,當前的status狀態仍是'pending'。這該如何改進代碼呢?
思路其實很簡單:
class ObjPromise { constructor(executor) { // promise狀態 this.status = 'pending'; // resolve回調成功,resolve方法裏的參數值 this.successVal = null; // reject回調成功,reject方法裏的參數值 this.failVal = null; // resolve的回調函數 this.onResolveCallback = []; // reject的回調函數 this.onRejectCallback = []; // 定義resolve函數 const resolve = (successVal) => { setTimeout(()=>{ if (this.status !== 'pending') { return; } this.status = 'resolve'; this.successVal = successVal; //執行全部resolve的回調函數 this.onResolveCallback.forEach(fn => fn()) }) }; // 定義reject const reject = (failVal) => { setTimeout(()=>{ if (this.status !== 'pending') { return; } this.status = 'reject'; this.failVal = failVal; //執行全部reject的回調函數 this.onRejectCallback.forEach(fn => fn()) }) }; try { // 將resolve函數給使用者 executor(resolve, reject) } catch (e) { // 執行拋出異常時 reject(e) } } // data爲返回值 // newResolve爲新的promise的resolve方法 // newReject爲新的promise的reject方法 resolvePromise(resPromise, data, newResolve, newReject) { if (resPromise === data) { return newReject(new TypeError('循環引用')) } if (!(data instanceof ObjPromise)) { return newResolve(data) } try { let then = data.then; const resolveFunction = (newData) => { this.resolvePromise(resPromise, newData, newResolve, newReject); }; const rejectFunction = (err) => { newReject(err); }; then.call(data, resolveFunction, rejectFunction) } catch (e) { // 錯誤處理 newReject(e); } } then(onResolved, onRejected) { const isFunction = (fn) => { return Object.prototype.toString.call(fn) === "[object Function]" }; onResolved = isFunction(onResolved) ? onResolved : (e) => e; onRejected = isFunction(onRejected) ? onRejected : err => { throw err }; // 定義這個變量保存要返回的promise對象 let resPromise; switch (this.status) { case "resolve": resPromise = new ObjPromise((resolve, reject) => { try { // 傳入的第一個函數 let data = onResolved(this.successVal); this.resolvePromise(resPromise, data, resolve, reject); } catch (e) { reject(e); } }); break; case "reject": resPromise = new ObjPromise((resolve, reject) => { try { // 傳入的第二個函數 let data = onRejected(this.failVal); this.resolvePromise(resPromise, data, resolve, reject); } catch (e) { reject(e); } }); break; case "pending": resPromise = new ObjPromise((resolve, reject) => { const resolveFunction = () => { try { // 傳入的第一個函數 let data = onResolved(this.successVal); this.resolvePromise(resPromise, data, resolve, reject); } catch (e) { reject(e); } }; const rejectFunction = () => { try { // 傳入的第二個函數 let data = onRejected(this.failVal); this.resolvePromise(resPromise, data, resolve, reject); } catch (e) { reject(e); } }; this.onResolveCallback.push(resolveFunction); this.onRejectCallback.push(rejectFunction); }); break; } return resPromise; } }
能夠用下面代碼測試一下:
new ObjPromise((resolve, reject) => { setTimeout(() => { resolve(1); }, 100) }).then((resp) => { console.log(resp); // 1 return 2 }).then((resp) => { console.log(resp); // 2 return new ObjPromise((resolve, reject) => { resolve(3) }) }).then((resp) => { console.log(resp); // 3 });
咱們如今已經基本完成了Promise的then方法啦。
到如今已經完成了promise最核心的兩個方法:constructor方法和then方法。不過Promise/A+還規定了一些其餘的方法,我們繼續來完成。
catch()方法就是能夠經過回調函數拿到reject的值,這個好辦,其實then方法已經實現了,轉接一下then方法就好了:
catch(onRejected) { return this.then(null, onRejected) }
這樣就實現了catch()方法
你們確定都見過Promise.resolve()或者Promise.resolve()用法。其實做用就是返回一個新的promise,而且內部調用resolve或者reject。
ObjPromise.resolve = (val) => { return new ObjPromise((resolve, reject) => { resolve(val) }) }; ObjPromise.reject = (val) => { return new ObjPromise((resolve, reject) => { reject(val) }) };
經過這兩種方法,我們能夠將現有的數據很方便的轉換成promise對象
all方法
all方法也是很經常使用的方法,它能夠傳入promise數組,當所有resolve或者有一個reject時,執行結束,固然返回的也是promise對象,來實現一下。
ObjPromise.all = (arrPromise) => { return new ObjPromise((resolve, reject) => { // 傳入類型必須爲數組 if(Array.isArray(arrPromise)){ return reject(new TypeError("傳入類型必須爲數組")) } // resp 保存每一個promise的執行結果 let resp = new Array(arrPromise.length); // 保存執行完成的promise數量 let doneNum = 0; for (let i = 0; arrPromise.length > i; i++) { // 將當前promise let nowPromise = arrPromise[i]; if (!(nowPromise instanceof ObjPromise)) { return reject(new TypeError("類型錯誤")) } // 將當前promise的執行結果存入到then中 nowPromise.then((item) => { resp[i] = item; doneNum++; if(doneNum === arrPromise.length){ resolve(resp); } }, reject) } }) };
來分析一下:
race方法也偶爾會用到,它能夠傳入promise數組,當哪一個promise執行完,則race就直接執行完,我們來實現一下:
ObjPromise.race = (arrPromise) => { return new Promise((resolve, reject) => { for (let i = 0; arrPromise.length > i; i++) { // 將當前promise let nowPromise = arrPromise[i]; if (!(nowPromise instanceof ObjPromise)) { return reject(new TypeError("類型錯誤")) }; nowPromise.then(resolve, reject); } }) };
來分析一下:
手撕完promise,趁熱再深刻學習一下ES7的新特性async/await。async/await至關牛逼:它是JavaScript 異步編程的一個重大改進,提供了在不阻塞主線程的狀況下使用同步代碼實現異步訪問資源的能力,而且使得代碼邏輯更加清晰。接下來我們就來深刻了解下async/await爲何能這麼牛逼。
async/await使用了Generator和Promise兩種技術,Promise我們已經掌握了,因此要再看一看Generator究竟是什麼。
生成器Generator
先了解一下生成器Generator是如何工做的,接着再學習Generator的底層實現機制——協程(Coroutine)
如何工做
生成器函數:生成器函數是一個帶星號函數,並且是能夠暫停執行和恢復執行的
先來看下面這段代碼:
function* genFun() { console.log("第一段") yield 'generator 1' console.log("第二段") return 'generator 2' } console.log('begin') let gen = genFun() console.log(gen.next().value) console.log('main 1') console.log(gen.next().value) console.log('main 2')
執行這段代碼,你會發現gen並非一次執行完的,而是全局代碼和gen代碼交替執行。這其實就是生成器函數的特性,它能夠暫停執行,也能夠恢復執行。
再來看下,它是具體是怎麼暫停執行和恢復執行的:
想要搞懂生成器函數如何暫停和恢復,要先了解一下協程的概念,協程是一種比線程更加輕量級的存在,能夠把協程當作是跑在線程上的任務:
從圖中結合代碼能夠看出協程的規則:
若是協程在執行期間,遇到了return,那麼JavaScript引擎會結束當前協程,並將return後面的內容返回給父協程。
其實規則總的來講:
再看async/await
已經知道,async/await使用了Generator和Promise兩種技術,其實往低層說就是微任務和協程的應用。如今Generator和Promise都已經深刻理解啦。可是微任務和協程是如何協做實現了async/await呢?
MDN:async是一個經過異步執行並隱式返回Promise做爲結果的函數。」
能夠執行下面代碼:
async function foo() { return 1 } console.log(foo()) // Promise {<resolved>: 1}
能夠看到調用async聲明的foo()函數返回了一個Promise對象,而且狀態是resolved。
MDN:await 表達式會暫停當前 async function 的執行,等待 Promise 處理完成。
若 Promise 正常處理(fulfilled),其回調的resolve函數參數做爲 await 表達式的值,繼續執行 async function。
若 Promise 處理異常(rejected),await 表達式會把 Promise 的異常緣由拋出。」
先來看下面這段代碼:
async function foo() { console.log(1) let a = await 99 console.log(a) } console.log(0) foo() console.log(3)
想要知道上面這段代碼執行結果如何,就先看看這段代碼的執行流程圖,我已經畫出來了:
結合上面這張流程圖,分析一下上面代碼的執行過程:
let newPromise = new Promise((resolve,reject){ resolve(99) })
而且在建立的過程當中遇到了resolve(99),JavaScript引擎會將該任務推入微任務隊列。
這一次,完全弄懂 Promise 原理
面試題:說說事件循環機制(滿分答案來了)
async/await 原理及執行順序分析