做者:深山螞蟻git
Promise 大夥太熟悉了,簡單的你都會,可是「刁鑽」的大廠面試官可能會問的不那麼同樣,好比拋出幾道題,讓你來看看 Promise 的執行過程。github
那麼咱們就來一塊兒來深刻剖析 Promise 的註冊微任務和執行的完整過程。能正確的使用 Promise 且能作到知其然知其因此然~。不再怕面試官的 Promise 問題了。web
咱們一般學習 Promise 都是基於 Promises/A+ 的實現。可是我不得不告訴你,本文還將分析該 js 實現和 webkit 內核的 JS 引擎的 Promise 的實現差別。具體到代碼運行上的差別。面試
本文總體思路採起 代碼例子 + 剖析講解 的方式來解讀,作到人人能懂,人人能理解的目的。chrome
絕不誇張,若是所有讀懂本文,那麼 Promise 的註冊和執行過程都將所向披靡,深刻你的骨髓,你就是 Promise 大神! ~~~~~~~npm
本文已代碼解讀的方式來學習整個過程。這裏提供了五段代碼,若是你都能理解清楚,徹底正確的說出 output 過程,那麼厲害大牛如你,我在這裏給你豎個大拇指,祝賀你對 Promise 的執行過程已經瞭如指掌。promise
固然可能你也未必真正瞭解核心,能正確的理解和解釋這個過程,不妨看看題目的解釋。
固然若是是和我同樣的菜鳥,那麼咱們就一塊兒來看看吧~瀏覽器
看答案前,先本身默默算下輸出結果吧。微信
new Promise((resolve, reject) => { console.log("外部promise"); resolve(); }) .then(() => { console.log("外部第一個then"); return new Promise((resolve, reject) => { console.log("內部promise"); resolve(); }) .then(() => { console.log("內部第一個then"); }) .then(() => { console.log("內部第二個then"); }); }) .then(() => { console.log("外部第二個then"); }); 複製代碼
這個輸出仍是比較簡單的,外部第一個 new Promise 執行,執行完 resolve ,而後執行外部第一個 then 。外部第一個 then 方法裏面 return 一個 Promise,這個 return ,表明 外部的第二個 then 的執行須要等待 return 以後的結果。固然會先執行完內部兩個 then 以後,再執行 外部的第二個 then ,機智如你,徹底正確。markdown
output:
外部promise
外部第一個then
內部promise
內部第一個then
內部第二個then
外部第二個then
複製代碼
new Promise((resolve, reject) => { console.log("外部promise"); resolve(); }) .then(() => { console.log("外部第一個then"); new Promise((resolve, reject) => { console.log("內部promise"); resolve(); }) .then(() => { console.log("內部第一個then"); }) .then(() => { console.log("內部第二個then"); }); }) .then(() => { console.log("外部第二個then"); }); 複製代碼
這段代碼和第一段代碼就相差一個 return ,然而結果確是不同的。
那這個怎麼理解呢?
咱們核心要看 then 的回調函數是啥時候註冊的,咱們知道,事件機制是 「先註冊先執行」,即數據結構中的 「隊列」 的模式,first in first out。那麼重點咱們來看下他們誰先註冊的。
外部的第二個 then 的註冊,須要等待 外部的第一個 then 的同步代碼執行完成。 當執行內部的 new Promise 的時候,而後碰到 resolve,resolve 執行完成,表明此時的該 Promise 狀態已經扭轉,以後開始內部的第一個 .then 的微任務的註冊,此時同步執行完成。咱們知道須要執行的動做是一個微任務,那麼天然要先執行完同步任務,好比以下:
new Promise((resolve, reject) => { resolve(); console.log(111); }) .then(() => { consle.log(222); }) 複製代碼
這個代碼顯然優先輸出執行 1111,再執行 222。 由於 222 的輸出是微任務的執行,111 是同步執行。
同理回到上面的代碼,內部的 resolve 以後,固然是先執行內部的 new Promise 的第一個 then 的註冊,這個 new Promise 執行完成,當即同步執行了後面的 .then 的註冊。
然而這個內部的第二個 then 是須要第一個 then 的的執行完成來決定的,而第一個 then 的回調是沒有執行,僅僅只是執行了同步的 .then 方法的註冊,因此會進入等待狀態。
這個時候,外部的第一個 then 的同步操做已經完成了,而後開始註冊外部的第二個 then,此時外部的同步任務也都完成了。同步操做完成以後,那麼開始執行微任務,咱們發現 內部的第一個 then 是優先於外部的第二個 then 的註冊,因此會執行完內部的第一個 then 以後,而後註冊內部的第二個 then ,而後執行外部的第二個 then ,而後再執行內部的第二個 then。
output:
外部promise
外部第一個then
內部promise
內部第一個then
外部第二個then
內部第二個then
複製代碼
咱們發現,這裏顯然是執行完一個 then ,接着會註冊該 then 以後的下一個 then,按照任務隊列的原理,咱們能夠發現,內外 then 是交替執行,而後交替註冊的。因此纔會出現輸出內外交替內容。
另外,我這裏所說的 then 的註冊,是指微任務隊列的註冊,並非 .then 的方法的執行,實際上 .then 方法的執行,咱們能夠理解爲僅僅只是初始化而已。若是看過源碼的會知道,.then 的執行確實是同步的,內部是再開啓一個 new Promise ,可是因爲上一個狀態未流轉,該 then 並不會此時註冊到微任務隊列中,而是會等待上一個的執行完成,因此咱們把 .then 沒註冊微任務就理解成尚沒執行是沒有問題的。
new Promise((resolve, reject) => { console.log("外部promise"); resolve(); }) .then(() => { console.log("外部第一個then"); let p = new Promise((resolve, reject) => { console.log("內部promise"); resolve(); }) p.then(() => { console.log("內部第一個then"); }) p.then(() => { console.log("內部第二個then"); }); }) .then(() => { console.log("外部第二個then"); }); 複製代碼
這段代碼的差別,就是內部的 Promise 的代碼的寫法變了,再也不是鏈式調用。
這裏怎麼理解呢?
這裏在執行內部的 new Promise 的 resolve 執行完成以後(扭轉了該 Promise 的狀態),new Promise 以後的兩個同步 p.then 是兩個執行代碼語句,都是同步執行,天然是會同步註冊完。
兩種方式的最主要的區別是:
因此這裏的代碼執行就比較清晰了,內部都執行完成以後(由於都優先於外部的第二個 then 的註冊),再執行外部的第二個 then :
output:
外部promise
外部第一個then
內部promise
內部第一個then
內部第二個then
外部第二個then
複製代碼
let p = new Promise((resolve, reject) => { console.log("外部promise"); resolve(); }) p.then(() => { console.log("外部第一個then"); new Promise((resolve, reject) => { console.log("內部promise"); resolve(); }) .then(() => { console.log("內部第一個then"); }) .then(() => { console.log("內部第二個then"); }); }) p.then(() => { console.log("外部第二個then"); }); 複製代碼
這段代碼中,外部的註冊採用了非鏈式調用的寫法,根據上面的講解,咱們知道了外部代碼的 p.then 是並列同步註冊的。因此代碼在內部的 new Promise 執行完,p.then 就都同步註冊完了。
內部的第一個 then 註冊以後,就開始執行外部的第二個 then 了(外部的第二個 then 和 外部的第一個 then 都是同步註冊完了)。而後再依次執行內部的第一個 then ,內部的第二個 then。
output:
外部promise
外部第一個then
內部promise
外部第二個then
內部第一個then
內部第二個then
複製代碼
我相信,若是能看懂上面的四段代碼以後,對 Promise 的執行和註冊很是瞭解了。
若是仍是不太懂,麻煩多看幾遍,相信你必定能懂~~~~~~~~
核心思想:
Promise 的 then 的 註冊微任務隊列 和 執行 是分離的。
註冊 : 是徹底遵循 JS 和 Promise 的代碼的執行過程。
執行 : 先 同步,再 微任務 ,再 宏觀任務。
只有分開理解上述,才能真正理解它們的執行順序~~~~~~~~~~~~~~~~
通過上面仔細深度文字解析以後,我相信你會豁然開朗了。
再來一道鞏固的題目:
new Promise((resolve, reject) => { console.log("外部promise"); resolve(); }) .then(() => { console.log("外部第一個then"); new Promise((resolve, reject) => { console.log("內部promise"); resolve(); }) .then(() => { console.log("內部第一個then"); }) .then(() => { console.log("內部第二個then"); }); return new Promise((resolve, reject) => { console.log("內部promise2"); resolve(); }) .then(() => { console.log("內部第一個then2"); }) .then(() => { console.log("內部第二個then2"); }); }) .then(() => { console.log("外部第二個then"); }); 複製代碼
這段代碼,其實就是結合了第一道題目和第二道題目綜合而成。
外部的第二個 then ,依賴於內部的 return 的執行結果,因此會等待 return 執行完成。
內部的第一段 new Promise 變成和內部的第二段 new Promise 的交替輸出了,理解方式和第二段代碼同樣。
output:
外部promise
外部第一個then
內部promise
內部promise2
內部第一個then
內部第一個then2
內部第二個then
內部第二個then2
外部第二個then
複製代碼
咱們知道 ES6 的 Promise 是須要考慮向下兼容的,開發當中每每沒有用系統內核的 Promise ,而是使用 npm install promise 來引入的。那麼 promise 的 js 實現和瀏覽器的實現是徹底一致的嗎?
按照上面的四段代碼的解析,咱們理解到了,Promise 的 then 的執行,是依賴於上一個 then 的執行完成以後,即 resolve 狀態以後,纔開始註冊到微任務隊列中的。
咱們一塊兒看一道題目,這裏的區別是 then 返回了一個 Promise.resolve();
new Promise((resolve, reject) => { console.log('外部promise'); resolve(); }) .then(() => { console.log('外部第一個then'); new Promise((resolve, reject) => { console.log('內部promise'); resolve(); }) .then(() => { console.log('內部第一個then'); return Promise.resolve(); }) .then(() => { console.log('內部第二個then'); }) }) .then(() => { console.log('外部第二個then'); }) .then(() => { console.log('外部第三個then'); }) 複製代碼
咱們先忽略內部第一個 then 的 return ,按照上面所學習到的,正常理解,咱們能得出依然是內外交替註冊和運行。
output:
外部promise
外部第一個then
內部promise
內部第一個then
外部第二個then
內部第二個then
外部第三個then
複製代碼
此題執行順序圖:
上面咱們是使用 Promise 的 js 實現的代碼輸出的結果。
然而你把這段代碼放在 chrome/safari 上跑一下,發現結果不同,以下是 webkit 內核瀏覽器跑出來的結果。
這個是什麼緣由呢?
爲啥多了一個 return Promise.resolve(),就把外層的 then 都執行完了呢?
要理解這個,咱們仍是要從註冊和執行來區分理解。
在執行輸出 「內部第一個 then 」以後,碰到 return Promise.resolve();咱們就來分析這個 Promise.resolve();
Promise/A+ 的實現:
執行 return Promise.resolve() ,建立一個 Promise 實例,將 Promise 實例設置爲 resolve 狀態,這個 Promise.resolve() 是同步的,且該 Promise 已經完成了,因此他並不會影響到其餘 then 的註冊。因此上述咱們分析是徹底正確的。
以下是 Promise.resolve 的實現,咱們發現,徹底是同步的,因此不影響最終結果。
Promise.resolve = function (value) { if (value instanceof Promise) return value; if (value === null) return NULL; if (value === undefined) return UNDEFINED; if (value === true) return TRUE; if (value === false) return FALSE; if (value === 0) return ZERO; if (value === '') return EMPTYSTRING; if (typeof value === 'object' || typeof value === 'function') { try { var then = value.then; if (typeof then === 'function') { return new Promise(then.bind(value)); } } catch (ex) { return new Promise(function (resolve, reject) { reject(ex); }); } } return valuePromise(value); }; 複製代碼
Promise 的 瀏覽器(webkit)的實現:
執行 return Promise.resolve() ,建立一個 Promise 實例,執行 resolve ,此時將該 Promise 的 resolve 的 value(這裏是undefined) 進入微任務隊列,將該 Promise 的狀態扭轉爲 resolve。而後接着執行了以前註冊好的 "外部第二個then" ,而後註冊 「外部第三個then」 ,接着執行 「內部第一個then」 的 return 的 resolve 的這個 undefined value 的 Promise,執行完成以後,而後註冊下一個then ,可是沒有下一個 then 了,執行完成,整個 return 任務完成,本次同步任務也執行完成,接着執行註冊的 「外部第三個then」 ,執行完成以後,註冊 「外部第四個then」,此時 」內部第一個then「 執行完成,註冊 」內部第二個then」,最後執行完「外部第四個then」,再執行 剛剛註冊的「內部第二個then」.
源代碼以下:
void Promise::Resolver::Resolve(Handle<Value> value) { i::Handle<i::JSObject> promise = Utils::OpenHandle(this); i::Isolate* isolate = promise->GetIsolate(); LOG_API(isolate, "Promise::Resolver::Resolve"); ENTER_V8(isolate); EXCEPTION_PREAMBLE(isolate); i::Handle<i::Object> argv[] = { promise, Utils::OpenHandle(*value) }; has_pending_exception = i::Execution::Call( isolate, isolate->promise_resolve(), isolate->factory()->undefined_value(), arraysize(argv), argv, false).is_null(); EXCEPTION_BAILOUT_CHECK(isolate, /* void */ ;); } 複製代碼
PromiseResolve = function PromiseResolve(promise, x) { PromiseDone(promise, +1, x, promiseOnResolve) } function PromiseDone(promise, status, value, promiseQueue) { if (GET_PRIVATE(promise, promiseStatus) === 0) { PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue), status); PromiseSet(promise, status, value); } } 複製代碼
有了上面的理解以後,若是外層再加一個 then ,那麼也知道結果了,執行完剛剛註冊的 「內部第二個then」,以後,開始執行註冊的 「外部第五個then」。
結合上面已經學會的 Promise 的執行順序,你應該能答出以下這道題的答案了吧,若是還不會,能夠考慮再仔細看一篇。
new Promise((resolve, reject) => { console.log("外部promise"); resolve(); }) .then(() => { console.log("外部第一個then"); new Promise((resolve, reject) => { console.log("內部promise"); resolve(); }) .then(() => { console.log("內部第一個then"); }) .then(() => { console.log("內部第二個then"); }); return new Promise((resolve, reject) => { console.log("內部promise2"); resolve(); }) .then(() => { console.log("內部第一個then2"); }) .then(() => { console.log("內部第二個then2"); }); }) .then(() => { console.log("外部第二個then"); }); 複製代碼
若是你以爲這篇內容對你有價值,請點贊,並關注咱們的官網和咱們的微信公衆號(WecTeam),每週都有優質文章推送: