Promise-Polyfill源码解析(2)
在上篇文章Promise-Polyfill源碼解析(1)詳細分析了Promise構造函數部分的源碼,本篇我們繼續分析剩下的源碼。 本篇我們重點分析then方法,讓我們回憶下then方法的使用方式:首先這個方法屬于每個Promise對象,這說明then方法應該定義在Promise的原型鏈上;然后這個方法接收兩個回調函數,如果Promsie的狀態為已完成,則執行第一個回調,狀態為被拒絕,則執行第二個回調,這個說明then方法會等待Promise狀態改變才會去執行回調;最后then方法可以鏈式調用,如下:
Promise.resolve().then(function() {// ... }, function() {// ... }).then(function() {// ... }, function() {// ... }); 復制代碼了解了以上,我們來看then方法的源碼:
Promise.prototype.then = function(onFulfilled, onRejected) {// @ts-ignorevar prom = new this.constructor(noop);handle(this, new Handler(onFulfilled, onRejected, prom));return prom; }; 復制代碼正如我們所猜想的,then方法定義在Promise的構造函數上,每個Promise對象可以共享該方法。其接收兩個參數onFulfilled、onRejected。具體實現也非常簡潔,只有三行代碼,先來看第一行:
var prom = new this.constructor(noop); 復制代碼這句代碼用new操作符實例化了一個對象,并保存在prom變量中。new操作符的右邊一定是個構造函數,this指向當前Promise對象,其constructor屬性指向構造函數,所以this.constructor指向Promise構造函數。我們知道,Promise構造函數的參數為一個函數,這里傳入了noop,noop是什么?我們找到其定義:
function noop() {} 復制代碼我們發現noop只是個空函數。再來看最后一行代碼:
return prom; 復制代碼返回了prom對象,也就是說,then方法最后返回了一個Promise對象,這也就是then方法可以鏈式調用的原因所在! 有個疑問,為什么不直接返回this,而是返回新創建的Promise對象呢?其實是因為Promise的狀態改變時單向的,且只能改變一次。 然后重點來看下第二行代碼:
handle(this, new Handler(onFulfilled, onRejected, prom)); 復制代碼調用了handle函數,先不管handle做了什么,我們先關注其第二個實參:
new Handler(onFulfilled, onRejected, prom) 復制代碼其實例化了Handler對象,參數為then方法的兩個參數和prom對象,我們來看下其具體實現:
/*** @constructor*/ function Handler(onFulfilled, onRejected, promise) {this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;this.onRejected = typeof onRejected === 'function' ? onRejected : null;this.promise = promise; } 復制代碼Handler構造函數將傳入的參數分別賦值給實例對象的onFulfilled、onRejected、promise屬性,其中對onFulfilled和onRejected做了處理,若不是函數類型,則賦值為null。這說明,我們傳入給then方法的兩個參數可以不為函數類型,其內部會調整為null。 明白了第二個參數,我們來看handle函數具體做了什么:
function handle(self, deferred) {while (self._state === 3) {self = self._value;}if (self._state === 0) {self._deferreds.push(deferred);return;}self._handled = true;Promise._immediateFn(function() {var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;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;}resolve(deferred.promise, ret);}); } 復制代碼首先是一個while循環:
while (self._state === 3) {self = self._value;} 復制代碼self指的是當前Promise對象,如果self._state的值為3,則將self._value賦值給self。我們在上篇文章分析過,_state屬性值為3,則說明_value值為一個Promise對象。那么這個循環的結果就是,直到_value屬性值不為Promise對象,為什么要這么處理呢?我們來看下規范是怎么說的: 如果 x 為 Promise,則使promise接收x的狀態
- 如果 x 處于pendding,promise需要保持為pendding狀態直至x被解決或拒絕
- 如果 x 處于fulFilled,用相同的值執行 promise
- 如果 x 處于rejected,用相同的據因拒絕 promise 總結起來就是,如果_value屬性值為Promise對象,則結果取決于嵌套最內層Promise的狀態。 接下來是一個條件判斷:
如果self._state屬性為0,則將deferred壓入self._deferreds數組,并結束此次函數調用。其中deferred為傳入的Handler實例對象,我們在上篇里分析過,_state屬性值為0表示Promise的狀態為pendding,我們可以猜測到,狀態為pedding,也就是Promise的狀態并未改變,then方法不知道要執行哪個回調,所在要先保存。那么為什么是保存在一個數組里,而不是保存在一個變量里,難道有很多個?其實還真可能有很多個,因為then方法可以被多次調用:
可以看到,每個then方法的回調都被執行了。 再來看下面的代碼: self._handled = true; 復制代碼上篇文章也分析過,_handled屬性用來標記Promise是否被處理,這里將其賦值為true,說明當前Promise對象已經被處理了。 最后來看最后一段代碼:
Promise._immediateFn(function() {... }); 復制代碼調用了Promise._immediateFn方法,并傳入了一個回調函數。先來看Promise._immediateFn的定義:
// Use polyfill for setImmediate for performance gains Promise._immediateFn =(typeof setImmediate === 'function' &&function(fn) {setImmediate(fn);}) ||function(fn) {setTimeoutFunc(fn, 0);}; 復制代碼這里判斷setImmediate是否是函數類型,成里則賦值為function(fn) { setImmediate(fn) },否則賦值為function(fn) { setTimeoutFunc(fn, 0) },其中setTimeoutFunc是setTimeout的別名:
var setTimeoutFunc = setTimeout; 復制代碼setImmediate是Node.js里的global對象的屬性,而setTimeout是瀏覽器環境里window對象的屬性,所以Promise._immediate是兼容兩個環境所做處理的代碼。為什么要再包一層閉包呢?應該是兼容參數的數量。 到這我們也明白了,then方法的回調是異步執行,其實更具體是在micro隊列中,這里我們就不展開了。 回到Promise._immediateFn的回調參數:
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; 復制代碼上篇文章分析過,self._state屬性值為1表示Promise的狀態為已完成,為2表示狀態為被決絕。那么這句代碼的意思是,根據Promise的狀態,將then方法的完成回調或決絕回調賦值給cb變量。 再來看下面的條件判斷:
if (cb === null) {(self._state === 1 ? resolve : reject)(deferred.promise, self._value);return; } 復制代碼cb變量為null,也就是我們傳入給then方法的參數不是函數類型,這里會根據Promise的狀態執行resolve或reject函數,并結束此次調用。注意傳入的參數,deferred.promise和self._value,也就是說,用Promise的值去改變在then方法內創建的Promise對象的狀態。總結起來就是,若then方法未傳入對應的回調,那么Promise的值會被傳遞到下一次then方法中:
再來看最后一段代碼:
var ret; try {ret = cb(self._value); } catch (e) {reject(deferred.promise, e);return; } resolve(deferred.promise, ret); 復制代碼忽略try..catch,核心是這樣的:
var ret = cb(self._value); resolve(deferred.promise, ret); 復制代碼將self._value作為參數,調用cb函數,返回值保存在ret變量中,再以ret變量為參數調用resolve函數。這里的意思就是,將cb函數的返回值作為Promise的值傳遞給下一個then方法:
當然,若拋出異常,則將原因作為Promise的值,傳遞給下一個then方法: reject(deferred.promise, e); return; 復制代碼至此,Promise源碼的核心部分已經分析完了,我們可以發現,閱讀源碼可以了解Promise的內部的工作機制,當出現問題時,我們也能快速定位原因。鼓勵大家去閱讀源碼! 當然還有catch、all、race等方法,將在下一篇文章繼續分析。
總結
以上是生活随笔為你收集整理的Promise-Polyfill源码解析(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 东莞首办工业机器人成果交流会听众爆满
- 下一篇: MyBatis(4)动态SQL