摘要:規范鏈接規范中文鏈接本篇文章將從的使用角度來剖析源碼具體實現。但是對于則不一樣,回調函數通過遞歸調用自己從而保證其值不為類型才結束,并將賦值到數組,最后直到所有的數組都處理完畢由統一的方法結束當前的操作,進入處理流程。
開篇
最近在 github 上看到了一個 extremely lightweight Promise polyfill 實現,打開源碼發現只有240行,果然極其輕量級,于是帶著驚嘆和好奇的心理去了解了下其具體實現。
源碼的 github 地址:promise-polyfill
Promise 對于前端來說,是個老生常談的話題,Promise 的出現解決了 js 回調地域的問題。目前市面上有很多 Promise 庫,但其最終實現都要遵從 Promise/A+ 規范,這里對規范不做解讀,有興趣的可以查看鏈接內容。
Promise/A+規范鏈接
Promise/A+規范中文鏈接
本篇文章將從 Promise 的使用角度來剖析源碼具體實現。
API 列表Promise // 構造函數 Promise.prototype.then Promise.prototype.catch Promise.prototype.finally // 靜態方法 Promise.resolve Promise.reject Promise.race Promise.all源碼解析 構造函數
使用
Promise 使用第一步,構造實例,傳入 Function 形參,形參接收兩個 Function 類型參數resolve, reject
const asyncTask = () => {}; const pro = new Promise((resolve, reject) => { asyncTask((err, data) => { if (err) { reject(err); } else { resolve(data); } }); });
源碼
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()函數實現有很大用處。
Promise 實例的內部變量介紹名稱 | 類型 | 默認值 | 描述 |
---|---|---|---|
_state | Number | 0 | Promise內部狀態碼 |
_handled | Boolean | false | onFulfilled,onRejected是否被處理過 |
_value | Any | undefined | Promise 內部值,resolve 或者 reject返回的值 |
_deferreds | Array | [] | 存放 Handle 實例對象的數組,緩存 then 方法傳入的回調 |
_state枚舉值類型
_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 內部 resolve reject finale 方法上面說到,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 方法的注意點
1、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 流程。
2、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、catch、finally 方法
第二步,調用 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
核心邏輯:handle方法內部實現上面說了這么多,最終的 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 要么提供帶返回值的回調,要么不提供回調函數
靜態方法:racePromise.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 競賽的機制,誰跑的快執行誰。
靜態方法:allPromise.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源碼注釋版
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94207.html
摘要:盤點一下,模式反應了典型的控制權問題。異步狀態管理與控制權提到控制權話題,怎能少得了這樣的狀態管理工具。狀態管理中的控制主義和極簡主義了解了異步狀態中的控制權問題,我們再從全局角度進行分析。 控制權——這個概念在編程中至關重要。比如,輪子封裝層與業務消費層對于控制權的爭奪,就是一個很有意思的話題。這在 React 世界里也不例外。表面上看,我們當然希望輪子掌控的事情越多越好:因為抽象層...
摘要:盤點一下,模式反應了典型的控制權問題。異步狀態管理與控制權提到控制權話題,怎能少得了這樣的狀態管理工具。狀態管理中的控制主義和極簡主義了解了異步狀態中的控制權問題,我們再從全局角度進行分析。 控制權——這個概念在編程中至關重要。比如,輪子封裝層與業務消費層對于控制權的爭奪,就是一個很有意思的話題。這在 React 世界里也不例外。表面上看,我們當然希望輪子掌控的事情越多越好:因為抽象層...
摘要:雖然有著各種各樣的不同,但是相同的是,他們前端優化不完全指南前端掘金篇幅可能有點長,我想先聊一聊閱讀的方式,我希望你閱讀的時候,能夠把我當作你的競爭對手,你的夢想是超越我。 如何提升頁面渲染效率 - 前端 - 掘金Web頁面的性能 我們每天都會瀏覽很多的Web頁面,使用很多基于Web的應用。這些站點看起來既不一樣,用途也都各有不同,有在線視頻,Social Media,新聞,郵件客戶端...
摘要:原理剖析第篇之服務端啟動工作原理分析下一大致介紹由于篇幅過長難以發布,所以本章節接著上一節來的,上一章節為原理剖析第篇之服務端啟動工作原理分析上那么本章節就繼續分析的服務端啟動,分析的源碼版本為二三四章節請看上一章節詳見原理剖析第篇之 原理剖析(第 011 篇)Netty之服務端啟動工作原理分析(下) - 一、大致介紹 1、由于篇幅過長難以發布,所以本章節接著上一節來的,上一章節為【原...
閱讀 1640·2023-04-25 20:36
閱讀 2049·2021-09-02 15:11
閱讀 1177·2021-08-27 13:13
閱讀 2653·2019-08-30 15:52
閱讀 4589·2019-08-29 17:13
閱讀 1001·2019-08-29 11:09
閱讀 1491·2019-08-26 11:51
閱讀 833·2019-08-26 10:56