最近在 github 上看到了一個 extremely lightweight Promise polyfill 實現,打開源碼發現只有240行,果真極其輕量級,因而帶着驚歎和好奇的心理去了解了下其具體實現。
源碼的 github 地址:promise-polyfill前端
Promise 對於前端來講,是個老生常談的話題,Promise 的出現解決了 js 回調地域的問題。目前市面上有不少 Promise 庫,但其最終實現都要聽從 Promise/A+ 規範,這裏對規範不作解讀,有興趣的能夠查看連接內容。
Promise/A+規範連接
Promise/A+規範中文連接node
本篇文章將從 Promise 的使用角度來剖析源碼具體實現。git
Promise // 構造函數 Promise.prototype.then Promise.prototype.catch Promise.prototype.finally // 靜態方法 Promise.resolve Promise.reject Promise.race Promise.all
使用
Promise 使用第一步,構造實例,傳入 Function 形參,形參接收兩個 Function 類型參數resolve, rejectgithub
const asyncTask = () => {}; const pro = new Promise((resolve, reject) => { asyncTask((err, data) => { if (err) { reject(err); } else { resolve(data); } }); });
源碼c#
function Promise(fn) { if (!(this instanceof Promise)) throw new TypeError('Promises must be constructed via new'); if (typeof fn !== 'function') throw new TypeError('not a function'); this._state = 0; this._handled = false; this._value = undefined; this._deferreds = []; doResolve(fn, this); } function doResolve(fn, self) { // done變量保護 resolve 和 reject 只執行一次 // 這個done在 Promise.race()函數中有用 var done = false; try { // 當即執行 Promise 傳入的 fn(resolve,reject) fn( function(value) { // resolve 回調 if (done) return; done = true; resolve(self, value); }, function(reason) { // reject 回調 if (done) return; done = true; reject(self, reason); } ); } catch (ex) { if (done) return; done = true; reject(self, ex); } }
Promise必須經過構造函數實例化來使用,傳入 Promise 構造函數的形參 fn 在doResolve方法內是 當即調用執行 的,並無異步(指放入事件循環隊列)處理。doResolve內部針對 fn 函數的回調參數作了封裝處理,done變量保證了 resolve reject 方法只執行一次,這在後面說到的Promise.race()函數實現有很大用處。segmentfault
名稱 | 類型 | 默認值 | 描述 |
---|---|---|---|
_state | Number | 0 | Promise內部狀態碼 |
_handled | Boolean | false | onFulfilled,onRejected是否被處理過 |
_value | Any | undefined | Promise 內部值,resolve 或者 reject返回的值 |
_deferreds | Array | [] | 存放 Handle 實例對象的數組,緩存 then 方法傳入的回調 |
_state枚舉值類型api
_state === 0 // pending _state === 1 // fulfilled,執行了resolve函數,而且_value instanceof Promise === true _state === 2 // rejected,執行了reject函數 _state === 3 // fulfilled,執行了resolve函數,而且_value instanceof Promise === false
注意:這裏_state區分了1 和 3 兩種狀態,下面會解釋緣由數組
/** * Handle 構造函數 * @param onFulfilled resolve 回調函數 * @param onRejected reject 回調函數 * @param promise 下一個 promise 實例對象 * @constructor */ function Handler(onFulfilled, onRejected, promise) { this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; this.onRejected = typeof onRejected === 'function' ? onRejected : null; this.promise = promise; }
_deferreds數組的意義:當在 Promise 內部調用了異步處理任務時,pro.then(onFulfilled,onRejected)傳入的兩個函數不會當即執行,因此此時會把當前的回調和下一個 pro 對象關聯緩存起來,待到 resolve 或者 reject觸發調用時,會去 forEach 這個_deferreds數組中的每一個 Handle 實例去處理對應的 onFulfilled,onRejected 方法。promise
上面說到,doResolve 內部作了 fn 的當即執行,並保證 resolve 和 reject 方法只執行一次,接下來講說resolve 和 reject 內部具體作了什麼緩存
function resolve(self, newValue) { try { // resolve 的值不能爲自己 this 對象 // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.'); // 針對 resolve 值爲 Promise 對象的狀況處理 if ( newValue && (typeof newValue === 'object' || typeof newValue === 'function') ) { var then = newValue.then; if (newValue instanceof Promise) { self._state = 3; self._value = newValue; finale(self); return; } else if (typeof then === 'function') { // 兼容類 Promise 對象的處理方式,對其 then 方法繼續執行 doResolve doResolve(bind(then, newValue), self); return; } } // resolve 正常值的流程,_state = 1 self._state = 1; self._value = newValue; finale(self); } catch (e) { reject(self, e); } } function reject(self, newValue) { self._state = 2; self._value = newValue; finale(self); } function finale(self) { // Promise reject 狀況,可是 then 方法未提供 reject 回調函數參數 或者 未實現 catch 函數 if (self._state === 2 && self._deferreds.length === 0) { Promise._immediateFn(function() { if (!self._handled) { Promise._unhandledRejectionFn(self._value); } }); } for (var i = 0, len = self._deferreds.length; i < len; i++) { // 這裏調用以前 then 方法傳入的onFulfilled, onRejected函數 // self._deferreds[i] => Handler 實例對象 handle(self, self._deferreds[i]); } self._deferreds = null; }
resolve,reject 是由用戶在異步任務裏面觸發的回調函數
調用 resolve reject 方法的注意點
一、newValue不能爲當前的 this 對象,即下面的這樣寫法是錯誤的
const pro = new Promise((resolve)=>{setTimeout(function () { resolve(pro); },1000)}); pro.then(data => console.log(data)).catch(err => {console.log(err)});
由於resolve作了 try catch 的操做,直接會進入 reject 流程。
二、newValue能夠爲另外一個Promise 對象類型實例, resolve 的值返回的是另外一個 Promise 對象實例的內部的_value,而不是其自己 Promise 對象。便可以這樣寫
const pro1 = new Promise((resolve)=>{setTimeout(function () { resolve(100); },2000)}); const pro = new Promise((resolve)=>{setTimeout(function () { resolve(pro1); },1000)}); pro.then(data => console.log('resolve' + data)).catch(err => {console.log('reject' + err)}); // 輸出結果:resolve 100 // data 並非pro1對象
具體緣由就在 resolve 方法體內部作了newValue instanceof Promise的判斷,並將當前的_state=3,self._value = newValue,而後進入 finale 方法體,在 handle 方法作了核心處理,這個下面介紹 handle 方法會說到;
這裏有一個注意點,resolve 的 value 多是其餘框架的 Promise(好比:global.Promise,nodejs 內部的 Promise 實現) 構造實例,因此在typeof then === 'function'條件下作了doResolve(bind(then, newValue), self);的從新調用,繼續執行當前類型的 Promise then 方法,即又從新回到了doResolve流程。
若是這裏的實現方式稍微調整下,即無論newValue是自身的 Promise 實例仍是其餘框架實現的 Promise實例,都執行doResolve(bind(then, newValue), self)也能行得通,只不過會多執行 then 方式一次,從代碼性能上說,上面的實現方式會更好。參照代碼以下
function resolve(self, newValue) { try { // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.'); if ( newValue && (typeof newValue === 'object' || typeof newValue === 'function') ) { // 這裏簡單粗暴處理,不管是 Promise 仍是 global.Promise // 都直接調用doResolve var then = newValue.then; if (typeof then === 'function') { doResolve(bind(then, newValue), self); return; } } // resolve 正常值的流程,_state = 1 self._state = 1; self._value = newValue; finale(self); } catch (e) { reject(self, e); } }
全部 resolve 和 reject 的值最終都會去到finale函數中去處理,只不過在這裏的_state狀態會有所不一樣;當Promise 出現reject的狀況時,而沒有提供 onRejected 函數時,內部會打印一個錯誤出來,提示要捕獲錯誤。代碼實現即
const pro = new Promise((resolve,reject)=>{setTimeout(function () { reject(100); },1000)}); pro.then(data => console.log(data)); // 會報錯 pro.then(data => console.log(data)).catch(); // 會報錯 pro.then(data => console.log(data)).catch(()=>{}); // 不會報錯 pro.then(data => console.log(data),()=>{}) // 不會報錯
第二步,調用 then 方法來處理回調,支持無限鏈式調用,then 方法第一個參數成功回調,第二個參數失敗或者異常回調
源碼
function noop() {} Promise.prototype.then = function(onFulfilled, onRejected) { var prom = new this.constructor(noop); handle(this, new Handler(onFulfilled, onRejected, prom)); return prom; }; Promise.prototype['catch'] = function(onRejected) { return this.then(null, onRejected); }; Promise.prototype['finally'] = function(callback) { var constructor = this.constructor; return this.then( function(value) { return constructor.resolve(callback()).then(function() { return value; }); }, function(reason) { return constructor.resolve(callback()).then(function() { return constructor.reject(reason); }); } ); };
Promise.prototype.then方法內部構造了一個新的Promsie 實例並返回,這樣從 api 角度解決了 Promise 鏈式調用的問題,並且值得注意的是,每一個 then 方法返回的都是一個新的 Promise 對象,並非當前的 this連接調用方式。最終的處理都會調用 handle 方法。
catch方法在 then 方法上作了一個簡單的封裝,因此從這裏也能夠看出,then 方法的形參並非必傳的,catch 只接收onRejected。
finally方法不論是調用了 then 仍是 catch,最終都會執行到finally的 callback
上面說了這麼多,最終的 resolve reject 回調處理都會進入到 handle 方法中,來處理onFulfilled 和 onRejected,先看源碼
Promise._immediateFn = (typeof setImmediate === 'function' && function(fn) { setImmediate(fn); }) || function(fn) { setTimeoutFunc(fn, 0); }; function handle(self, deferred) { // 若是當前的self._value instanceof Promise // 將self._value => self,接下來處理新 Promise while (self._state === 3) { self = self._value; } // self._state=== 0 說明尚未執行 resolve || reject 方法 // 此處將 handle 掛起 if (self._state === 0) { self._deferreds.push(deferred); return; } self._handled = true; // 經過事件循環異步來作回調的處理 Promise._immediateFn(function() { // deferred.promise :第一個 Promise then 方法 返回的新 Promise 對象 // 這裏調用下一個 Promise 對象的 then 方法的回調函數 // 若是當前 Promise resolve 了,則調用下一個 Promise 的 resolve方法,反之,則調用下一個 Promise 的 reject 回調 // 若是當前 Promise resolve 了,則調用下一個 Promise 的 resolve方法 // cb回調方法:若是本身有onFulfilled||onRejected方法,則執行本身的方法;若是沒有,則調用下一個 Promise 對象的onFulfilled||onRejected var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; // 本身沒有回調函數,進入下一個 Promise 對象的回調 if (cb === null) { (self._state === 1 ? resolve : reject)(deferred.promise, self._value); return; } // 本身有回調函數,進入本身的回調函數 var ret; try { ret = cb(self._value); } catch (e) { reject(deferred.promise, e); return; } // 處理下一個 Promise 的 then 回調方法 // ret 做爲上一個Promise then 回調 return的值 => 返回給下一個Promise then 做爲輸入值 resolve(deferred.promise, ret); }); }
self._state === 3,說明當前 resolve(promise)方法回傳的值類型爲 Promise 對象,
即 self._value instanceOf Promise === true, 將 self=self._value,即當前處理變動到了新的 Promise 對象上 ,若是當前 promise對象內部狀態是fulfilled或者 rejected,則直接處理onFulfilled 或者 onRejected回調;若是仍然是 padding 狀態,則繼續等待。這就很好的解釋了爲何resolve(pro1),pro.then的回調取的值倒是 pro1._value.
從使用角度來看
const pro1 = new Promise(resolve=>{setTimeout(()=>{resolve(100)},1000)}) // 執行耗時1s 的異步任務 pro.then(()=>pro1).then(data => console.log(data)).catch(err => {}); // 輸出結果: 正常打印了100,data並非當前的pro1對象
pro1內部是耗時1s 的異步任務,此時self._state === 0,即內部是 Padding 狀態,則將deferred對象 push 到_deferreds數組裏面,而後等待 pro1內部調用resolve(100)時,繼續上面resolve方法體執行
const pro1 = new Promise(resolve=>resolve(100)}) // 執行同步任務 pro.then(()=>pro1).then(data => console.log(data)).catch(err => {}); // 輸出結果: 正常打印了100,data並非當前的pro1對象
可是若是pro1內部是同步任務,當即執行的話,當前的self._state === 1,即調過 push 到_deferreds數組的操做,執行最後的onFulfilled, onRejected回調,onFulfilled, onRejected會被放入到事件循環隊列裏面執行,即執行到了Promise._immediateFn
Promise._immediateFn回調函數放到了事件循環隊列裏面來執行
這裏的deferred對象存放了當前的onFulfilled和onRejected回調函數和下一個 promise 對象。
當前對象的onFulfilled和onRejected若是存在時,則執行本身的回調;
pro.then(data => data}).then(data => data).catch(err => {}); // 正確寫法: 輸出兩次 data
注意:then 方法必定要作 return 下一個值的操做,由於當前的 ret 值會被帶入到下一個 Promise 對象,即 resolve(deferred.promise, ret)。若是不提供返回值,則第二個 then 的 data 會變成 undefined,即這樣的錯誤寫法
pro.then(data => {}}).then(data => data).catch(err => {}); // 錯誤寫法: 第二個 then 方法的 data 爲 undefined
若是onFulfilled和onRejected回調不存在,則執行下一個 promise 的回調並攜帶當前的_value 值。便可以這樣寫
pro.then().then().then().then(data => {}).catch(err => {}); // 正確寫法: 第四個 then 方法仍然能取到第一個pro 的內部_value 值 // 固然前面的三個 then 寫起來毫無用處
因此針對下面的狀況:當第一個 then 提供了 reject 回調,後面又跟了個 catch 方法。
當 reject 時,會優先執行第一個 Promise 的onRejected回調函數,catch 是在下一個 Promise 對象上的捕獲錯誤方法
pro.then(data => data,err => err).catch(err => err);
最終總結:resolve 要麼提供帶返回值的回調,要麼不提供回調函數
Promise.race = function(values) { return new Promise(function(resolve, reject) { for (var i = 0, len = values.length; i < len; i++) { // 由於doResolve方法內部 done 變量控制了對 resolve reject 方法只執行一次的處理 // 因此這裏實現很簡單,清晰明瞭,最快的 Promise 執行了 resolve||reject,後面相對慢的 // Promise都不執行 values[i].then(resolve, reject); } }); };
用法
Promise.race([pro1,pro2,pro3]).then()
race的實現很是巧妙,對當前的 values(必須是 Promise 數組) for 循環執行每一個 Promise 的 then 方法,resolve, reject方法對於全部race中 promise 對象都是公用的,從而利用doResolve內部的 done變量,保證了最快執行的 Promise 能作 resolve reject 的回調,從而達到了多個Promise race 競賽的機制,誰跑的快執行誰。
Promise.all = function(arr) { return new Promise(function(resolve, reject) { if (!arr || typeof arr.length === 'undefined') throw new TypeError('Promise.all accepts an array'); var args = Array.prototype.slice.call(arr); if (args.length === 0) return resolve([]); var remaining = args.length; function res(i, val) { try { // 若是 val 是 Promise 對象的話,則執行 Promise,直到 resolve 了一個非 Promise 對象 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; } } // 用當前resolve||reject 的值重寫 args[i]{Promise} 對象 args[i] = val; // 直到全部的 Promise 都執行完畢,則 resolve all 的 Promise 對象,返回args數組結果 if (--remaining === 0) { resolve(args); } } catch (ex) { // 只要其中一個 Promise 出現異常,則所有的 Promise 執行退出,進入 catch異常處理 // 由於 resolve 和 reject 回調有 done 變量的保證只能執行一次,因此其餘的 Promise 都不執行 reject(ex); } } for (var i = 0; i < args.length; i++) { res(i, args[i]); } }); };
用法
Promise.all([pro1,pro2,pro3]).then()
all 等待全部的 Promise 都執行完畢,纔會執行 Promise.all().then()回調,只要其中一個出錯,則直接進入錯誤回調,由於對於全部 all 中 promise 對象 reject 回調是公用的,利用doResolve內部的 done變量,保證一次錯誤終止全部操做。
可是對於 resolve 則不同, resolve 回調函數經過 res 遞歸調用本身,從而保證其值_value不爲 Promise 類型才結束,並將_value 賦值到 args 數組,最後直到全部的數組Promise都處理完畢由統一的 resolve 方法結束當前的 all 操做,進入 then 處理流程。
本篇針對 Promise 的全部 api 作了詳細的代碼解釋和使用場景,篇幅可能過長,看起來比較費力,若是有寫的不對的地方歡迎指正。
最後附上個人 github 源碼註釋版連接 promise源碼註釋版