最簡實現Promise,支持異步鏈式調用(20行)

前言

在面試的時候,常常會有面試官讓你實現一個 Promise,若是參照 A+規範來實現的話,可能面到天黑都結束不了。前端

說到 Promise,咱們首先想到的最核心的功能就是異步鏈式調用,本篇文章就帶你用 20 行代碼實現一個能夠異步鏈式調用的 Promise。面試

這個 Promise 的實現不考慮任何異常狀況,只考慮代碼最簡短,從而便於讀者理解核心的異步鏈式調用原理。數組

代碼

先給代碼吧,真就 20 行。promise

function Promise(fn) {
  this.cbs = [];

  const resolve = (value) => {
    setTimeout(() => {
      this.data = value;
      this.cbs.forEach((cb) => cb(value));
    });
  }

  fn(resolve);
}

Promise.prototype.then = function (onResolved) {
  return new Promise((resolve) => {
    this.cbs.push(() => {
      const res = onResolved(this.data);
      if (res instanceof Promise) {
        res.then(resolve);
      } else {
        resolve(res);
      }
    });
  });
};
複製代碼

核心案例

new Promise((resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 500);
})
  .then((res) => {
    console.log(res);
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(2);
      }, 500);
    });
  })
  .then(console.log);
複製代碼

本文將圍繞這個最核心的案例來說,這段代碼的表現以下:markdown

  1. 500ms 後輸出 1
  2. 500ms 後輸出 2

實現

構造函數

首先來實現 Promise 構造函數異步

function Promise(fn) {
  // Promise resolve時的回調函數集
  this.cbs = [];

  // 傳遞給Promise處理函數的resolve
  // 這裏直接往實例上掛個data
  // 而後把onResolvedCallback數組裏的函數依次執行一遍就能夠
  const resolve = (value) => {
    // 注意promise的then函數須要異步執行
    setTimeout(() => {
      this.data = value;
      this.cbs.forEach((cb) => cb(value));
    });
  }

  // 執行用戶傳入的函數 
  // 而且把resolve方法交給用戶執行
  fn(resolve);
}
複製代碼

好,寫到這裏先回過頭來看案例函數

const fn = (resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 500);
};

new Promise(fn);
複製代碼

分開來看,fn 就是用戶傳的函數,這個函數內部調用了 resolve 函數後,就會把 promise 實例上的 cbs 所有執行一遍。this

到此爲止咱們還不知道 cbs 這個數組裏的函數是從哪裏來的,接着往下看。spa

then

這裏是最重要的 then 實現,鏈式調用全靠它:prototype

Promise.prototype.then = function (onResolved) {
  // 這裏叫作promise2
  return new Promise((resolve) => {
    this.cbs.push(() => {
      const res = onResolved(this.data);
      if (res instanceof Promise) {
        // resolve的權力被交給了user promise
        res.then(resolve);
      } else {
        // 若是是普通值 就直接resolve
        // 依次執行cbs裏的函數 而且把值傳遞給cbs
        resolve(res);
      }
    });
  });
};
複製代碼

再回到案例裏

const fn = (resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 500);
};

const promise1 = new Promise(fn);

promise1.then((res) => {
  console.log(res);
  // user promise
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(2);
    }, 500);
  });
});
複製代碼

注意這裏的命名:

  1. 咱們把 new Promise 返回的實例叫作promise1

  2. Promise.prototype.then 的實現中,咱們構造了一個新的 promise 返回,叫它promise2

  3. 在用戶調用 then 方法的時候,用戶手動構造了一個 promise 而且返回,用來作異步的操做,叫它user promise

那麼在 then 的實現中,內部的 this 其實就指向promise1

promise2的傳入的fn 函數執行了一個 this.cbs.push(),實際上是往 promise1cbs數組中 push 了一個函數,等待後續執行。

Promise.prototype.then = function (onResolved) {
  // 這裏叫作promise2
  return new Promise((resolve) => {
    // 這裏的this實際上是promise1
    this.cbs.push(() => {});
  });
};
複製代碼

那麼重點看這個 push 的函數,注意,這個函數在 promise1 被 resolve 了之後纔會執行。

// promise2
return new Promise((resolve) => {
  this.cbs.push(() => {
    // onResolved就對應then傳入的函數
    const res = onResolved(this.data)
    // 例子中的狀況 用戶本身返回了一個user promise
    if (res instanceof Promise) {
      // user promise的狀況
      // 用戶會本身決定什麼時候resolve promise2
      // 只有promise2被resolve之後
      // then下面的鏈式調用函數纔會繼續執行
      res.then(resolve)
    } else {
      resolve(res)
    }
  })
})
複製代碼

若是用戶傳入給 then 的 onResolved 方法返回的是個 user promise,那麼這個user promise裏用戶會本身去在合適的時機 resolve promise2,那麼進而這裏的 res.then(resolve) 中的 resolve 就會被執行:

if (res instanceof Promise) {
    res.then(resolve)
}
複製代碼

結合下面這個例子來看:

new Promise((resolve) => {
  setTimeout(() => {
    // resolve1
    resolve(1);
  }, 500);
})
  // then1
  .then((res) => {
    console.log(res);
    // user promise
    return new Promise((resolve) => {
      setTimeout(() => {
        // resolve2
        resolve(2);
      }, 500);
    });
  })
  // then2
  .then(console.log);
複製代碼

then1這一整塊其實返回的是 promise2,那麼 then2 其實本質上是 promise2.then(console.log)

也就是說 then2註冊的回調函數,其實進入了promise2cbs 回調數組裏,又由於咱們剛剛知道,resolve2 調用了以後,user promise 會被 resolve,進而觸發 promise2 被 resolve,進而 promise2 裏的 cbs 數組被依次觸發。

這樣就實現了用戶本身寫的 resolve2 執行完畢後,then2 裏的邏輯纔會繼續執行,也就是異步鏈式調用

文章總結

本文只是簡單實現一個能夠異步鏈式調用的 promise,而真正的 promise 比它複雜不少不少,涉及到各類異常狀況、邊界狀況的處理。

promise A+規範仍是值得每個合格的前端開發去閱讀的。

但願這篇文章能夠對你有所幫助!

相關文章
相關標籤/搜索