天天看點

圖解 Promise 實作原理(四)—— Promise 靜态方法實作

本系列文章由淺入深逐漸實作 Promise,并結合流程圖、執行個體以及動畫進行示範,達到深刻了解 Promise 用法的目的。

本文首發于 vivo網際網路技術 微信公衆号 

連結: https://mp.weixin.qq.com/s/Lp_5BXdpm7G29Z7zT_S-bQ

作者:Morrain

了用法,原生提供了Promise對象。更多關于 Promise 的介紹請參考阮一峰老師的 ES6入門 之 Promise 對象。

很多同學在學習 Promise 時,知其然卻不知其是以然,對其中的用法了解不了。本系列文章由淺入深逐漸實作 Promise,并結合流程圖、執行個體以及動畫進行示範,達到深刻了解 Promise 用法的目的。

本系列文章有如下幾個章節組成:

  1. 圖解 Promise 實作原理(一)—— 基礎實作
  2. 圖解 Promise 實作原理(二)—— Promise 鍊式調用
  3. 圖解 Promise 實作原理(三)—— Promise 原型方法實作
  4. 圖解 Promise 實作原理(四)—— Promise 靜态方法實作

一、前言

上一節中,實作了 Promise 的原型方法。包括增加異常狀态,catch以及 finally。截至目前,Promise 的實作如下:

class Promise {
  callbacks = [];
  state = 'pending';//增加狀态
  value = null;//儲存結果
  constructor(fn) {
    fn(this._resolve.bind(this), this._reject.bind(this));
  }
  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      this._handle({
        onFulfilled: onFulfilled || null,
        onRejected: onRejected || null,
        resolve: resolve,
        reject: reject
      });
    });
  }
  catch(onError) {
    return this.then(null, onError);
  }
  finally(onDone) {
    if (typeof onDone !== 'function') return this.then();
 
    let Promise = this.constructor;
    return this.then(
      value => Promise.resolve(onDone()).then(() => value),
      reason => Promise.resolve(onDone()).then(() => { throw reason })
    );
  }
  _handle(callback) {
    if (this.state === 'pending') {
      this.callbacks.push(callback);
      return;
    }
 
    let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
 
    if (!cb) {//如果then中沒有傳遞任何東西
      cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
      cb(this.value);
      return;
    }
 
    let ret;
 
    try {
      ret = cb(this.value);
      cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
    } catch (error) {
      ret = error;
      cb = callback.reject
    } finally {
      cb(ret);
    }
 
  }
  _resolve(value) {
 
    if (value && (typeof value === 'object' || typeof value === 'function')) {
      var then = value.then;
      if (typeof then === 'function') {
        then.call(value, this._resolve.bind(this), this._reject.bind(this));
        return;
      }
    }
 
    this.state = 'fulfilled';//改變狀态
    this.value = value;//儲存結果
    this.callbacks.forEach(callback => this._handle(callback));
  }
  _reject(error) {
    this.state = 'rejected';
    this.value = error;
    this.callbacks.forEach(callback => this._handle(callback));
  }
}      

接下來再介紹一下 Promise 中靜态方法的實作,譬如 Promise.resolve、Promise.reject、Promise.all 和 Promise.race。其它靜态方法的實作也是類似的。

二、靜态方法

1、Promise.resolve && Promise.reject

除了前文中提到的 Promise執行個體的原型方法外,Promise 還提供了 Promise.resolve 和Promise.reject 方法。用于将非 Promise 執行個體包裝為 Promise 執行個體。例如:

Promise.resolve('foo')
// 等價于
new Promise(resolve => resolve('foo'))      

Promise.resolve 的參數不同對應的處理也不同,如果 Promise.resolve 的參數是一個 Promise的執行個體,那麼 Promise.resolve 将不做任何改動,直接傳回這個 Promise 執行個體,如果是一個基本資料類型,譬如上例中的字元串,Promise.resolve 就會建立一個 Promise 執行個體傳回。這樣當我們不清楚拿到的對象到底是不是 Promise 執行個體時,為了保證統一的行為,Promise.resolve 就變得很有用了。看一個例子:

const Id2NameMap = {};
const getNameById = function (id) {
 
  if (Id2NameMap[id]) return Id2NameMap[id];
 
  return new Promise(resolve => {
    mockGetNameById(id, function (name) {
      Id2NameMap[id] = name;
      resolve(name);
    })
  });
}
getNameById(id).then(name => {
  console.log(name);
});      

上面的場景我們會經常碰到,為了減少請求,經常會緩存資料,我們擷取到 id 對應的名字後,存到 Id2NameMap 對象裡,下次再通過 id 去請求 id 對應的 name 時先看 Id2NameMap裡有沒有,如果有就直接傳回對應的 name,如果沒有就發起異步請求,擷取到後放到 Id2NameMap 中去。

其實上面的代碼是有問題的,如果命中 Id2NameMap 裡的值,getNameById 傳回的結果就是 name,而不是 Promise 執行個體。此時 getNameById(id).then 會報錯。在我們不清楚傳回的是否是 Promise 執行個體的情況下,就可以使用 Promise.resolve 進行包裝:

Promise.resolve(getNameById(id)).then(name => {
  console.log(name);
});      

這樣一來,不管 getNameById(id) 傳回的是什麼,邏輯都沒有問題。看下面的Demo:

demo-Promise.resolve 的源碼

在實作 Promise.resolve 之前,我們先看下它的參數分為哪些情況:

(1)參數是一個 Promise 執行個體

如果參數是 Promise 執行個體,那麼 Promise.resolve 将不做任何修改、原封不動地傳回這個執行個體。

(2)參數是一個 thenable 對象

thenable 對象指的是具有 then 方法的對象,比如下面這個對象。

let thenable = {
  then: function(onFulfilled) {
    onFulfilled(42);
  }
};      

Promise.resolve 方法會将這個對象轉為 Promise 對象,然後就立即執行 thenable 對象的 then方法。

let thenable = {
  then: function(onFulfilled) {
    onFulfilled(42);
  }
};
 
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});      

上面代碼中,thenable對象的then方法執行後,對象p1的狀态就變為resolved,進而立即執行最後那個then方法指定的回調函數,輸出 42。

(3)參數不是具有 then 方法的對象,或根本就不是對象

如果參數是一個原始值,或者是一個不具有then方法的對象,則 Promise.resolve 方法傳回一個新的 Promise 對象,狀态為 resolved。

(4)不帶任務參數

Promise.resolve 方法允許調用時不帶參數,直接傳回一個 resolved 狀态的 Promise 對象。

static resolve(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 執行個體,是以狀态不一定。

Promise.reject 的實作源碼

2、Promise.all && Promise.race

Promise.all 接收一個 Promise 執行個體的數組,在所有這些 Promise 的執行個體都 fulfilled 後,按照 Promise 執行個體的順序傳回相應結果的數組。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p1'), 1000)
})
 
 
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p2'), 5000)
})
 
Promise.all([p1, p2]).then(rets => {
   console.log(rets) // ['p1','p2']
})      

Promise.all 的實作如下:

static all(promises) {
  return new Promise((resolve, reject) => {
    let fulfilledCount = 0
    const itemNum = promises.length
    const rets = Array.from({ length: itemNum })
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(result => {
        fulfilledCount++;
        rets[index] = result;
        if (fulfilledCount === itemNum) {
          resolve(rets);
        }
      }, reason => reject(reason));
    })
  })
}      

Promise.all 的實作源碼

Promise.race 也接收一個 Promise 執行個體的數組,與 Promise.all不同的是,是以傳回的結果是這些 Promise 執行個體中最先 fulfilled 的。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p1'), 1000)
})
 
 
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p2'), 5000)
})
 
Promise.race([p1, p2]).then(ret => {
   console.log(ret) // 'p1'
})      

Promise.race 的實作如下:

static race(promises) {
  return new Promise(function (resolve, reject) {
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(function (value) {
        return resolve(value)
      }, function (reason) {
        return reject(reason)
      })
    }
  })
}      

Promise.race 的實作源碼

三、總結

剛開始看 Promise 源碼的時候總不能很好的了解 then 和 resolve 函數的運作機理,但是如果你靜下心來,反過來根據執行 Promise 時的邏輯來推演,就不難了解了。這裡一定要注意的點是:Promise 裡面的 then 函數僅僅是注冊了後續需要執行的代碼,真正的執行是在 resolve 方法裡面執行的,理清了這層,再來分析源碼會省力的多。

現在回顧下 Promise 的實作過程,其主要使用了設計模式中的觀察者模式:

  1. 通過 Promise.prototype.then 和 Promise.prototype.catch 方法将觀察者方法注冊到被觀察者 Promise 對象中,同時傳回一個新的 Promise 對象,以便可以鍊式調用。
  2. 被觀察者管理内部 pending、fulfilled 和 rejected 的狀态轉變,同時通過構造函數中傳遞的 resolve 和 reject 方法以主動觸發狀态轉變和通知觀察者。

本系列圖文講解的是 Promise 的思想,實作的内容并不能完全滿足 Promise/A+ 規範的所有要求。

四、參考資料

  1. 【翻譯】Promises/A+規範
  2. 深入 Promise(一)——Promise 實作詳解
  3. 30分鐘,讓你徹底明白Promise原理

更多内容敬請關注 vivo 網際網路技術 微信公衆号

圖解 Promise 實作原理(四)—— Promise 靜态方法實作

注:轉載文章請先與微信号:Labs2020 聯系。

分享 vivo 網際網路技術幹貨與沙龍活動,推薦最新行業動态與熱門會議。