「全」手寫Promise的相關方法

原文發佈在:手寫Promise的相關方法javascript

摘要

Promise 做爲 JS 社區的異步解決方案,爲開發者提供了.then()Promise.resolve()Promise.reject()等基本方法。除此以外,爲了更方便地組合和控制多個的 Promise 實例,也提供了.all().race()等方法。前端

本文會在 Promise 的基本方法上,手動實現更高級的方法,來加深對 Promise 的理解:java

  • 🤔️ 實現Promise.all
  • 🤔️ 實現Promise.race
  • 🤔️ 實現Promise.any
  • 🤔️ 實現Promise.allSettled
  • 🤔️ 實現Promise.finally

⚠️ 完整代碼和用例請到github.com/dongyuanxin…git

實現 Promise.all

過程

Promise.all(iterators)返回一個新的 Promise 實例。iterators 中包含外界傳入的多個 promise 實例。es6

對於返回的新的 Promise 實例,有如下兩種狀況:github

  • 若是傳入的全部 promise 實例的狀態均變爲fulfilled,那麼返回的 promise 實例的狀態就是fulfilled,而且其 value 是 傳入的全部 promise 的 value 組成的數組。
  • 若是有一個 promise 實例狀態變爲了rejected,那麼返回的 promise 實例的狀態當即變爲rejected

代碼實現

實現思路:數組

  • 傳入的參數不必定是數組對象,能夠是"遍歷器"
  • 傳入的每一個實例不必定是 promise,須要用Promise.resolve()包裝
  • 藉助"計數器",標記是否全部的實例狀態均變爲fulfilled
Promise.myAll = function(iterators) {
  const promises = Array.from(iterators);
  const num = promises.length;
  const resolvedList = new Array(num);
  let resolvedNum = 0;

  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(value => {
          // 保存這個promise實例的value
          resolvedList[index] = value;
          // 經過計數器,標記是否全部實例均 fulfilled
          if (++resolvedNum === num) {
            resolve(resolvedList);
          }
        })
        .catch(reject);
    });
  });
};
複製代碼

實現 Promise.race

過程

Promise.race(iterators)的傳參和返回值與Promise.all相同。但其返回的 promise 的實例的狀態和 value,徹底取決於:傳入的全部 promise 實例中,最早改變狀態那個(不管是fulfilled仍是rejected)。promise

代碼實現

實現思路:異步

  • 某傳入實例pending -> fulfilled時,其 value 就是Promise.race返回的 promise 實例的 value
  • 某傳入實例pending -> rejected時,其 error 就是Promise.race返回的 promise 實例的 error
Promise.myRace = function(iterators) {
  const promises = Array.from(iterators);

  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(resolve)
        .catch(reject);
    });
  });
};
複製代碼

實現 Promise.any

我是專一前端的技術博客 「xin-tan.com」 的做者。您能夠經過Watch or Star文章倉庫 「github.com/dongyuanxin…」,或關注公衆號「心譚博客」來接收最新文章消息。函數

過程

Promise.any(iterators)的傳參和返回值與Promise.all相同。

若是傳入的實例中,有任一實例變爲fulfilled,那麼它返回的 promise 實例狀態當即變爲fulfilled;若是全部實例均變爲rejected,那麼它返回的 promise 實例狀態爲rejected

⚠️Promise.allPromise.any的關係,相似於,Array.prototype.everyArray.prototype.some的關係。

代碼實現

實現思路和Promise.all及其相似。不過因爲對異步過程的處理邏輯不一樣,所以這裏的計數器用來標識是否全部的實例均 rejected

Promise.any = function(iterators) {
  const promises = Array.from(iterators);
  const num = promises.length;
  const rejectedList = new Array(num);
  let rejectedNum = 0;

  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(value => resolve(value))
        .catch(error => {
          rejectedList[index] = error;
          if (++rejectedNum === num) {
            reject(rejectedList);
          }
        });
    });
  });
};
複製代碼

實現 Promise.allSettled

過程

Promise.allSettled(iterators)的傳參和返回值與Promise.all相同。

根據ES2020,此返回的 promise 實例的狀態只能是fulfilled。對於傳入的全部 promise 實例,會等待每一個 promise 實例結束,而且返回規定的數據格式。

若是傳入 a、b 兩個 promise 實例:a 變爲 rejected,錯誤是 error1;b 變爲 fulfilled,value 是 1。那麼Promise.allSettled返回的 promise 實例的 value 就是:

[{ status: "rejected", value: error1 }, { status: "fulfilled", value: 1 }];
複製代碼

代碼實現

實現中的計數器,用於統計全部傳入的 promise 實例。

const formatSettledResult = (success, value) =>
  success
    ? { status: "fulfilled", value }
    : { status: "rejected", reason: value };

Promise.allSettled = function(iterators) {
  const promises = Array.from(iterators);
  const num = promises.length;
  const settledList = new Array(num);
  let settledNum = 0;

  return new Promise(resolve => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(value => {
          settledList[index] = formatSettledResult(true, value);
          if (++settledNum === num) {
            resolve(settledList);
          }
        })
        .catch(error => {
          settledList[index] = formatSettledResult(false, error);
          if (++settledNum === num) {
            resolve(settledList);
          }
        });
    });
  });
};
複製代碼

Promise.all、Promise.any 和 Promise.allSettled 中計數器使用對比

這三個方法均使用了計數器來進行異步流程控制,下面表格橫向對比不一樣方法中計數器的用途,來增強理解:

方法名 用途
Promise.all 標記 fulfilled 的實例個數
Promise.any 標記 rejected 的實例個數
Promise.allSettled 標記全部實例(fulfilled 和 rejected)的個數

實現 Promise.prototype.finally

過程

它就是一個語法糖,在當前 promise 實例執行完 then 或者 catch 後,均會觸發。

舉個例子,一個 promise 在 then 和 catch 中均要打印時間戳:

new Promise(resolve => {
  setTimeout(() => resolve(1), 1000);
})
  .then(value => console.log(Date.now()))
  .catch(error => console.log(Date.now()));
複製代碼

如今這段必定執行的共同邏輯,就能夠用finally簡寫爲:

new Promise(resolve => {
  setTimeout(() => resolve(1), 1000);
}).finally(() => console.log(Date.now()));
複製代碼

能夠看出,Promise.prototype.finally 的執行與 promise 實例的狀態無關,不依賴於 promise 的執行後返回的結果值。其傳入的參數是函數對象。

代碼實現

實現思路:

  • 考慮到 promise 的 resolver 多是個異步函數,所以 finally 實現中,要經過調用實例上的 then 方法,添加 callback 邏輯
  • 成功透傳 value,失敗透傳 error
Promise.prototype.finally = function(cb) {
  return this.then(
    value => Promise.resolve(cb()).then(() => value),
    error =>
      Promise.resolve(cb()).then(() => {
        throw error;
      })
  );
};
複製代碼

參考連接

若是以爲有收穫,歡迎Watch or Star文章倉庫 「github.com/dongyuanxin…」,或掃碼關注公衆號「心譚博客」,解鎖更多文章。

相關文章
相關標籤/搜索