從運用角度漸進式理會Promise源碼

開篇

最近在 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 實例的內部變量引見

稱號範例默認值形貌
_stateNumber0Promise內部狀況碼
_handledBooleanfalseonFulfilled,onRejected是不是被處置懲罰過
_valueAnyundefinedPromise 內部值,resolve 或許 reject返回的值
_deferredsArray[]寄存 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 要麼供應帶返回值的回調,要麼不供應回調函數

靜態要領:race

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 比賽的機制,誰跑的快實行誰。

靜態要領:all

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源碼解釋版

    原文作者:frontmoment
    原文地址: https://segmentfault.com/a/1190000014368256
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞