作者聲明 本人將遷移至個人公眾號「前端Q」及「掘金」平臺寫文章。博客園的文章將不再及時更新發佈。歡迎大家關註公眾號「前端Q」及我的掘金主頁:https://juejin.im/user/5874526761ff4b006d4fd9a4/posts Promise 必須為以下三種狀態之一:等待態(Pe ...
作者聲明
本人將遷移至個人公眾號「前端Q」及「掘金」平臺寫文章。博客園的文章將不再及時更新發佈。歡迎大家關註公眾號「前端Q」及我的掘金主頁:https://juejin.im/user/5874526761ff4b006d4fd9a4/posts
Promise 必須為以下三種狀態之一:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected)。一旦Promise 被 resolve 或 reject,不能再遷移至其他任何狀態(即狀態 immutable)。
基本過程:
- 初始化 Promise 狀態(pending)
- 執行 then(..) 註冊回調處理數組(then 方法可被同一個 promise 調用多次)
- 立即執行 Promise 中傳入的 fn 函數,將Promise 內部 resolve、reject 函數作為參數傳遞給 fn ,按事件機制時機處理
- Promise里的關鍵是要保證,then方法傳入的參數 onFulfilled 和 onRejected,必須在then方法被調用的那一輪事件迴圈之後的新執行棧中執行。
真正的鏈式Promise是指在當前promise達到fulfilled狀態後,即開始進行下一個promise.
鏈式調用
先從 Promise 執行結果看一下,有如下一段代碼:
new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) resolve({ test: 2 }) reject({ test: 2 }) }, 1000) }).then((data) => { console.log('result1', data) },(data1)=>{ console.log('result2',data1) }).then((data) => { console.log('result3', data) }) //result1 { test: 1 } //result3 undefined
顯然這裡輸出了不同的 data。由此可以看出幾點:
- 可進行鏈式調用,且每次 then 返回了新的 Promise(2次列印結果不一致,如果是同一個實例,列印結果應該一致。
- 只輸出第一次 resolve 的內容,reject 的內容沒有輸出,即 Promise 是有狀態且狀態只可以由pending -> fulfilled或 pending-> rejected,是不可逆的。
- then 中返回了新的 Promise,但是then中註冊的回調仍然是屬於上一個 Promise 的。
基於以上幾點,我們先寫個基於 PromiseA+ 規範的只含 resolve 方法的 Promise 模型:
function Promise(fn){ let state = 'pending'; let value = null; const callbacks = []; this.then = function (onFulfilled){ return new Promise((resolve, reject)=>{ handle({ //橋梁,將新 Promise 的 resolve 方法,放到前一個 promise 的回調對象中 onFulfilled, resolve }) }) } function handle(callback){ if(state === 'pending'){ callbacks.push(callback) return; } if(state === 'fulfilled'){ if(!callback.onFulfilled){ callback.resolve(value) return; } const ret = callback.onFulfilled(value) //處理回調 callback.resolve(ret) //處理下一個 promise 的resolve } } function resolve(newValue){ const fn = ()=>{ if(state !== 'pending')return state = 'fulfilled'; value = newValue handelCb() } setTimeout(fn,0) //基於 PromiseA+ 規範 } function handelCb(){ while(callbacks.length) { const fulfiledFn = callbacks.shift(); handle(fulfiledFn); }; } fn(resolve) }
這個模型簡單易懂,這裡最關鍵的點就是在 then 中新創建的 Promise,它的狀態變為 fulfilled 的節點是在上一個 Promise的回調執行完畢的時候。也就是說當一個 Promise 的狀態被 fulfilled 之後,會執行其回調函數,而回調函數返回的結果會被當作 value,返回給下一個 Promise(也就是then 中產生的 Promise),同時下一個 Promise的狀態也會被改變(執行 resolve 或 reject),然後再去執行其回調,以此類推下去...鏈式調用的效應就出來了。
但是如果僅僅是例子中的情況,我們可以這樣寫:
new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) }, 1000) }).then((data) => { console.log('result1', data) //dosomething console.log('result3') }) //result1 { test: 1 } //result3
實際上,我們常用的鏈式調用,是用在非同步回調中,以解決"回調地獄"的問題。如下例子:
new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) }, 1000) }).then((data) => { console.log('result1', data) //dosomething return test() }).then((data) => { console.log('result2', data) }) function test(id) { return new Promise(((resolve) => { setTimeout(() => { resolve({ test: 2 }) }, 5000) })) } //基於第一個 Promise 模型,執行後的輸出 //result1 { test: 1 } //result2 Promise {then: ƒ}
用上面的 Promise 模型,得到的結果顯然不是我們想要的。認真看上面的模型,執行 callback.resolve 時,傳入的參數是 callback.onFulfilled 執行完成的返回,顯然這個測試例子返回的就是一個 Promise,而我們的 Promise 模型中的 resolve 方法並沒有特殊處理。那麼我們將 resolve 改一下:
function Promise(fn){ ... function resolve(newValue){ const fn = ()=>{ if(state !== 'pending')return if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){ const {then} = newValue if(typeof then === 'function'){ // newValue 為新產生的 Promise,此時resolve為上個 promise 的resolve //相當於調用了新產生 Promise 的then方法,註入了上個 promise 的resolve 為其回調 then.call(newValue,resolve) return } } state = 'fulfilled'; value = newValue handelCb() } setTimeout(fn,0) } ... }
用這個模型,再測試我們的例子,就得到了正確的結果:
new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) }, 1000) }).then((data) => { console.log('result1', data) //dosomething return test() }).then((data) => { console.log('result2', data) }) function test(id) { return new Promise(((resolve, reject) => { setTimeout(() => { resolve({ test: 2 }) }, 5000) })) } //result1 { test: 1 } //result2 { test: 2 }
顯然,新增的邏輯就是針對 resolve 入參為 Promise 的時候的處理。我們觀察一下 test 裡面創建的 Promise,它是沒有調用 then方法的。從上面的分析我們已經知道 Promise 的回調函數就是通過調用其 then 方法註冊的,因此 test 裡面創建的 Promise 其回調函數為空。
顯然如果沒有回調函數,執行 resolve 的時候,是沒辦法鏈式下去的。因此,我們需要主動為其註入回調函數。
我們只要把第一個 then 中產生的 Promise 的 resolve 函數的執行,延遲到 test 裡面的 Promise 的狀態為 onFulfilled 的時候再執行,那麼鏈式就可以繼續了。所以,當 resolve 入參為 Promise 的時候,調用其 then 方法為其註入回調函數,而註入的是前一個 Promise 的 resolve 方法,所以要用 call 來綁定 this 的指向。
基於新的 Promise 模型,上面的執行過程產生的 Promise 實例及其回調函數,可以用看下表:
Promise | callback |
---|---|
P1 | [{onFulfilled:c1(第一個then中的fn),resolve:p2resolve}] |
P2 (P1 調用 then 時產生) | [{onFulfilled:c2(第二個then中的fn),resolve:p3resolve}] |
P3 (P2 調用 then 時產生) | [] |
P4 (執行c1中產生[調用 test ]) | [{onFulfilled:p2resolve,resolve:p5resolve}] |
P5 (調用p2resolve 時,進入 then.call 邏輯中產生) | [] |
有了這個表格,我們就可以清晰知道各個實例中 callback 執行的順序是:
c1 -> p2resolve -> c2 -> p3resolve -> [] -> p5resolve -> []
以上就是鏈式調用的原理了。
reject
下麵我們再來補全 reject 的邏輯。只需要在註冊回調、狀態改變時加上 reject 的邏輯即可。
完整代碼如下:
function Promise(fn){ let state = 'pending'; let value = null; const callbacks = []; this.then = function (onFulfilled,onRejected){ return new Promise((resolve, reject)=>{ handle({ onFulfilled, onRejected, resolve, reject }) }) } function handle(callback){ if(state === 'pending'){ callbacks.push(callback) return; } const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected; const next = state === 'fulfilled'? callback.resolve:callback.reject; if(!cb){ next(value) return; } const ret = cb(value) next(ret) } function resolve(newValue){ const fn = ()=>{ if(state !== 'pending')return if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){ const {then} = newValue if(typeof then === 'function'){ // newValue 為新產生的 Promise,此時resolve為上個 promise 的resolve //相當於調用了新產生 Promise 的then方法,註入了上個 promise 的resolve 為其回調 then.call(newValue,resolve, reject) return } } state = 'fulfilled'; value = newValue handelCb() } setTimeout(fn,0) } function reject(error){ const fn = ()=>{ if(state !== 'pending')return if(error && (typeof error === 'object' || typeof error === 'function')){ const {then} = error if(typeof then === 'function'){ then.call(error,resolve, reject) return } } state = 'rejected'; value = error handelCb() } setTimeout(fn,0) } function handelCb(){ while(callbacks.length) { const fn = callbacks.shift(); handle(fn); }; } fn(resolve, reject) }
異常處理
異常通常是指在執行成功/失敗回調時代碼出錯產生的錯誤,對於這類異常,我們使用 try-catch 來捕獲錯誤,並將 Promise 設為 rejected 狀態即可。
handle代碼改造如下:
function handle(callback){ if(state === 'pending'){ callbacks.push(callback) return; } const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected; const next = state === 'fulfilled'? callback.resolve:callback.reject; if(!cb){ next(value) return; } try { const ret = cb(value) next(ret) } catch (e) { callback.reject(e); } }
我們實際使用時,常習慣註冊 catch 方法來處理錯誤,例:
new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) }, 1000) }).then((data) => { console.log('result1', data) //dosomething return test() }).catch((ex) => { console.log('error', ex) })
實際上,錯誤也好,異常也罷,最終都是通過reject實現的。也就是說可以通過 then 中的錯誤回調來處理。所以我們可以增加這樣的一個 catch 方法:
function Promise(fn){ ... this.then = function (onFulfilled,onRejected){ return new Promise((resolve, reject)=>{ handle({ onFulfilled, onRejected, resolve, reject }) }) } this.catch = function (onError){ this.then(null,onError) } ... }
Finally方法
在實際應用的時候,我們很容易會碰到這樣的場景,不管Promise最後的狀態如何,都要執行一些最後的操作。我們把這些操作放到 finally 中,也就是說 finally 註冊的函數是與 Promise 的狀態無關的,不依賴 Promise 的執行結果。所以我們可以這樣寫 finally 的邏輯:
function Promise(fn){ ... this.catch = function (onError){ this.then(null,onError) } this.finally = function (onDone){ this.then(onDone,onError) } ... }
resolve 方法和 reject 方法
實際應用中,我們可以使用 Promise.resolve 和 Promise.reject 方法,用於將於將非 Promise 實例包裝為 Promise 實例。如下例子:
Promise.resolve({name:'winty'}) Promise.reject({name:'winty'}) // 等價於 new Promise(resolve => resolve({name:'winty'})) new Promise((resolve,reject) => reject({name:'winty'}))
這些情況下,Promise.resolve 的入參可能有以下幾種情況:
- 無參數 [直接返回一個resolved狀態的 Promise 對象]
- 普通數據對象 [直接返回一個resolved狀態的 Promise 對象]
- 一個Promise實例 [直接返回當前實例]
- 一個thenable對象(thenable對象指的是具有then方法的對象) [轉為 Promise 對象,並立即執行thenable對象的then方法。]
基於以上幾點,我們可以實現一個 Promise.resolve 方法如下:
function Promise(fn){ ... this.resolve = function (value){ if (value && value instanceof Promise) { return value; } else if (value && typeof value === 'object' && typeof value.then === 'function'){ let then = value.then; return new Promise(resolve => { then(resolve); }); } else if (value) { return new Promise(resolve => resolve(value)); } else { return new Promise(resolve => resolve()); } } ... }
Promise.reject與Promise.resolve類似,區別在於Promise.reject始終返回一個狀態的rejected的Promise實例,而Promise.resolve的參數如果是一個Promise實例的話,返回的是參數對應的Promise實例,所以狀態不一 定。 因此,reject 的實現就簡單多了,如下:
function Promise(fn){ ... this.reject = function (value){ return new Promise(function(resolve, reject) { reject(value); }); } ... }
Promise.all
入參是一個 Promise 的實例數組,然後註冊一個 then 方法,然後是數組中的 Promise 實例的狀態都轉為 fulfilled 之後則執行 then 方法。這裡主要就是一個計數邏輯,每當一個 Promise 的狀態變為 fulfilled 之後就保存該實例返回的數據,然後將計數減一,當計數器變為 0 時,代表數組中所有 Promise 實例都執行完畢。
function Promise(fn){ ... this.all = function (arr){ var args = Array.prototype.slice.call(arr); return new Promise(function(resolve, reject) { if(args.length === 0) return resolve([]); var remaining = args.length; function res(i, val) { try { if(val && (typeof val === 'object' || typeof val === 'function')) { var then = val.then; if(typeof then === 'function') { then.call(val, function(val) { res(i, val); }, reject); return; } } args[i] = val; if(--remaining === 0) { resolve(args); } } catch(ex) { reject(ex); } } for(var i = 0; i < args.length; i++) { res(i, args[i]); } }); } ... }
Promise.race
有了 Promise.all 的理解,Promise.race 理解起來就更容易了。它的入參也是一個 Promise 實例數組,然後其 then 註冊的回調方法是數組中的某一個 Promise 的狀態變為 fulfilled 的時候就執行。因為 Promise 的狀態只能改變一次,那麼我們只需要把 Promise.race 中產生的 Promise 對象的 resolve 方法,註入到數組中的每一個 Promise 實例中的回調函數中即可。
function Promise(fn){ ... this.race = function(values) { return new Promise(function(resolve, reject) { for(var i = 0, len = values.length; i < len; i++) { values[i].then(resolve, reject); } }); } ... }
總結
Promise 源碼不過幾百行,我們可以從執行結果出發,分析每一步的執行過程,然後思考其作用即可。其中最關鍵的點就是要理解 then 函數是負責註冊回調的,真正的執行是在 Promise 的狀態被改變之後。而當 resolve 的入參是一個 Promise 時,要想鏈式調用起來,就必須調用其 then 方法(then.call),將上一個 Promise 的 resolve 方法註入其回調數組中。
參考資料
完整 Promise 模型
function Promise(fn) { let state = 'pending' let value = null const callbacks = [] this.then = function (onFulfilled, onRejected) { return new Promise((resolve, reject) => { handle({ onFulfilled, onRejected, resolve, reject, }) }) } this.catch = function (onError) { this.then(null, onError) } this.finally = function (onDone) { this.then(onDone, onError) } this.resolve = function (value) { if (value && value instanceof Promise) { return value } if (value && typeof value === 'object' && typeof value.then === 'function') { const { then } = value return new Promise((resolve) => { then(resolve) }) } if (value) { return new Promise(resolve => resolve(value)) } return new Promise(resolve => resolve()) } this.reject = function (value) { return new Promise(((resolve, reject) => { reject(value) })) } this.all = function (arr) { const args = Array.prototype.slice.call(arr) return new Promise(((resolve, reject) => { if (args.length === 0) return resolve([]) let remaining = args.length function res(i, val) { try { if (val && (typeof val === 'object' || typeof val === 'function')) { const { then } = val if (typeof then === 'function') { then.call(val, (val) => { res(i, val) }, reject) return } } args[i] = val if (--remaining === 0) { resolve(args) } } catch (ex) { reject(ex) } } for (let i = 0; i < args.length; i++) { res(i, args[i]) } })) } this.race = function (values) { return new Promise(((resolve, reject) => { for (let i = 0, len = values.length; i < len; i++) { values[i].then(resolve, reject) } })) } function handle(callback) { if (state === 'pending') { callbacks.push(callback) return } const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected const next = state === 'fulfilled' ? callback.resolve : callback.reject if (!cb) { next(value) return } try { const ret = cb(value) next(ret) } catch (e) { callback.reject(e) } } function resolve(newValue) { const fn = () => { if (state !== 'pending') return if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { const { then } = newValue if (typeof then === 'function') { // newValue 為新產生的 Promise,此時resolve為上個 promise 的resolve // 相當於調用了新產生 Promise 的then方法,註入了上個 promise 的resolve 為其回調 then.call(newValue, resolve, reject) return } } state = 'fulfilled' value = newValue handelCb() } setTimeout(fn, 0) } function reject(error) { const fn = () => { if (state !== 'pending') return if (error && (typeof error === 'object' || typeof error === 'function')) { const { then } = error if (typeof then === 'function') { then.call(error, resolve, reject) return } } state = 'rejected' value = error handelCb() } setTimeout(fn, 0) } function handelCb() { while (callbacks.length) { const fn = callbacks.shift() handle(fn) } } fn(resolve, reject) }
最後
覺得內容有幫助可以關註下我的掘金主頁:https://juejin.im/user/5874526761ff4b006d4fd9a4/posts
覺得內容有幫助可以關註下我的公眾號 「前端Q」,一起學習成長~~