mini-promise 實現原理講解

文章首發於個人博客 https://github.com/mcuking/bl...

實現源碼請查閱 https://github.com/mcuking/bl...git

本文主要是闡述如何一步步實現一個符合 Promises/A+ 規範 的 Promise。github

Promise 實現

在實現以前,讓咱們先看一段使用 Promise 的示例代碼:數組

new Promise((resolve, reject) => {
  setTimeout(resolve('hello world'), 1000);
})
.then(
  (msg) => console.log(msg), 
  (err) => console.error(err)
);

Promise 實例化對象過程的實現

首先咱們來先實現前半部分的功能,即:promise

new Promise((resolve, reject) => {
  setTimeout(resolve('hello world'), 1000);
})

根據 Promises/A+ 規範 咱們能夠分解一下須要作的事情:函數

  1. 首先採用一個類來實現 Promise;
  2. 這個類的實例有一個狀態屬性,來表示 Promise 對象實例的三種狀態:pending、resolved、rejected;
  3. Promise 實例化對象的時候會接收一個函數,會在實例化的時候就當即執行;
  4. 上一點的當即執行函數有兩個參數:resolve 方法和 reject 方法。這兩個方法主要是用來改變 Promise 對象實例的狀態,以及執行成功/失敗回調函數,這個邏輯放到後面來實現。

所以能夠初步寫出以下代碼:優化

class Promise {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }

    this.value = undefined; // Promise的值
    this.status = 'pending'; // Promise當前的狀態

    const resolve = value => {
      // 成功後的一系列操做(狀態的改變、成功回調執行)
    };

    const reject = reason => {
      // 失敗後的一系列操做(狀態的改變、失敗回調執行)
    };

    try {
      // 考慮到執行executor的過程當中有可能出錯,因此咱們用try/catch塊給包起來,而且在出錯後以catch到的值reject掉這個Promise
      executor(resolve, reject); // 執行executor
    } catch (e) {
      reject(e);
    }
  }
}

接下來咱們來分析如何實現第二部分--then 方法:this

new Promise((resolve, reject) => {
  setTimeout(resolve('hello world'), 1000);
})
.then(
  (msg) => console.log(msg), 
  (err) => console.error(err)
);

根據 Promises/A+ 規範 ,then 方法會接收兩個參數,分別是成功和失敗的回調函數。當調用 resolve 方法將 Promise 狀態從 pending 改成 resolved,並執行傳入的成功回調函數;調用 reject 方法將 Promise 狀態從 pending 改成 rejected,並執行失敗回調函數。spa

須要注意的是,一個 Promise 實例對象的 then 方法會被調用屢次,也就說 then 方法能夠接收多個成功/失敗回調函數,因此須要使用數組來接收這些回調函數。當 Promise 實例對象的狀態從 pending 變成 resovled/rejected 時,就遍歷存儲回調函數的數組執行全部的成功/失敗回調函數。code

下面是對應的實現代碼:對象

class Promise {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }

    this.value = undefined; // Promise的值
    this.status = 'pending'; // Promise當前的狀態
    this.onResolvedCallbacks = []; // Promise resolve時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面
    this.onRejectedCallbacks = []; // Promise reject時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面

    const resolve = value => {
      // 當前狀態爲pending時纔會執行
      if (this.status === 'pending') {
        this.status = 'resolved';
        this.value = value;
        for (let i = 0; i < this.onResolvedCallbacks.length; i++) {
          this.onResolvedCallbacks[i](value);
        }
      }
    };

    const reject = reason => {
      // 當前狀態爲pending時纔會執行
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.value = reason;
        for (let i = 0; i < this.onRejectedCallbacks.length; i++) {
          this.onRejectedCallbacks[i](reason);
        }
      }
    };

    try {
      // 考慮到執行executor的過程當中有可能出錯,因此咱們用try/catch塊給包起來,而且在出錯後以catch到的值reject掉這個Promise
      executor(resolve, reject); // 執行executor
    } catch (e) {
      reject(e);
    }
  }
}

Promise 對象實例的 then 方法的實現

從上面的實現中,讀者可能會有一個疑問:執行 resolve 和 reject 方法時,爲何會須要先判斷當前 Promise 實例對象狀態是否爲 pending。若是是 pending,纔會遍歷執行回調函數數組中的回調函數呢?

由於當 Promise 實例對象已經處於 resolved 或 rejected 狀態時,傳入的對應回調函數時就須要被當即執行,而且只會被執行一次。再次調用 resolve 或 reject 方法不會再作任何操做。

例如當 Promise 實例已經處於 resolved 狀態時,調用 then 方法接收成功回調函數時,該函數會被當即執行。同理處於 rejected 狀態時,會當即執行 then 方法接收的失敗回調函數。

另外須要明確的一點是,根據 Promises/A+ 規範,then 方法必須返回一個 Promise 對象,所以執行完 then 方法後,須要再返回一個新的 Promise 實例。

那麼咱們就來根據剛纔的分析來實現一下 then 方法。下面對應的實現代碼:

// 增長then方法
class Promise {
  constructor(executor) {
    // 代碼同上
  }

  // then方法接收兩個參數,onResolved,onRejected,分別爲Promise成功或失敗後的回調
  then(onResolved, onRejected) {
    let self = this;
    let promise2;

    // 根據標準,若是then的參數不是function,則咱們須要忽略它,此處以以下方式處理
    onResolved = typeof onResolved === 'function' ? onResolved : function(v) {};
    onRejected = typeof onRejected === 'function' ? onRejected : function(r) {};

    if (self.status === 'resolved') {
      return (promise2 = new Promise(function(resolve, reject) {
        onResolved(self.data);
      }));
    }

    if (self.status === 'rejected') {
      return (promise2 = new Promise(function(resolve, reject) {
        onRejected(self.data);
      }));
    }

    if (self.status === 'pending') {
      return (promise2 = new Promise(function(resolve, reject) {
        self.onResolvedCallbacks.push(function(value) {
          onResolved(value);
        });
        self.onRejectedCallbacks.push(function(reason) {
          onRejected(reason);
        });
      }));
    }
  }
}
  • 當前 promise1 處於 pending 狀態時,執行 then 方法時返回一個 promise2 的同時,在 promise2 對應的當即執行函數中將接收到的回調函數塞入 promise1 的回調隊列中;
  • 當處於 resolved 時,則在返回的 promise2 對應的當即執行函數中調用傳入的成功回調函數;
  • 當處於 rejeted 時,則在返回的 promise2 對應的當即執行函數中調用傳入的失敗回調函數。

這裏讀者可能會有一個疑問,爲何須要在 promise2 對應的當即執行函數中執行塞入回調函數到隊列或當即執行回調函數的邏輯?直接在外面執行這些邏輯不就能夠了麼?這就涉及到了下個要實現的功能了--Promise 鏈式調用

Promise 鏈式調用-回調函數返回值問題

根據 Promises/A+ 規範

promise2 = promise1.then(onFulfilled, onRejected);

若是 onFulfilled 或者 onRejected 返回一個值 x ,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)。具體規則以下:

image

這裏咱們只考慮 x 爲 Promise 或非對象和函數的值的狀況:

若是 onResolved/onRejected 的返回值 x 是一個 Promise 對象,直接取它的結果作爲 promise2 的結果,即 x 接管了 promise2 的狀態:若是 x 處於 pending 狀態, promise2 需保持爲 pending 狀態直至 x 被 resolve/reject 掉,若是 x 處於 resolved 狀態,用相同的 value 執行 promise2,若是 x 處於 rejected 狀態,用相同的 reason 拒絕 promise2。

若是 x 不爲對象或者函數,則以 x 爲參數執行 promise2。

下面是對應的實現代碼:

// 針對上一個Promise爲pending時,上一個then返回值進行優化
class Promise {
  constructor(executor) {
    // 同上
  }

  // then方法接收兩個參數,onResolved,onRejected,分別爲Promise成功或失敗後的回調
  then(onResolved, onRejected) {
    let self = this;
    let promise2;

    // 根據標準,若是then的參數不是function,則咱們須要忽略它,此處以以下方式處理
    onResolved = typeof onResolved === 'function' ? onResolved : function(v) {};
    onRejected = typeof onRejected === 'function' ? onRejected : function(r) {};

    if (self.status === 'resolved') {
      // 若是promise1(此處即爲this/self)的狀態已經肯定而且是resolved,咱們調用onResolved
      // 由於考慮到有可能throw,因此咱們將其包在try/catch塊裏
      return (promise2 = new Promise(function(resolve, reject) {
        try {
          let x = onResolved(self.value);
          if (x instanceof Promise) {
            // 若是onResolved的返回值是一個Promise對象,直接取它的結果作爲promise2的結果
            x.then(resolve, reject);
          }
          resolve(x); // 不然,以它的返回值作爲promise2的結果
        } catch (e) {
          reject(e); // 若是出錯,以捕獲到的錯誤作爲promise2的結果
        }
      }));
    }

    // 此處與前一個if塊的邏輯幾乎相同,區別在於所調用的是onRejected函數,就再也不作過多解釋
    if (self.status === 'rejected') {
      return (promise2 = new Promise(function(resolve, reject) {
        try {
          let x = onRejected(self.value);
          if (x instanceof Promise) {
            x.then(resolve, reject);
          }
          reject(x); // 不然,以它的返回值作爲promise2的結果
        } catch (e) {
          reject(e);
        }
      }));
    }

    if (self.status === 'pending') {
      // 若是當前的Promise還處於pending狀態,咱們並不能肯定調用onResolved仍是onRejected,
      // 只能等到Promise的狀態肯定後,才能確實如何處理。
      // 因此咱們須要把咱們的**兩種狀況**的處理邏輯作爲callback放入promise1(此處即this/self)的回調隊列裏
      // 邏輯自己跟第一個if塊內的幾乎一致,此處不作過多解釋
      return (promise2 = new Promise(function(resolve, reject) {
        self.onResolvedCallbacks.push(function(value) {
          try {
            let x = onResolved(value);
            if (x instanceof Promise) {
              x.then(resolve, reject);
            }
          } catch (e) {
            reject(e);
          }
        });

        self.onRejectedCallbacks.push(function(reason) {
          try {
            let x = onRejected(reason);
            if (x instanceof Promise) {
              x.then(resolve, reject);
            }
          } catch (e) {
            reject(e);
          }
        });
      }));
    }
  }
}

Promise 鏈式調用-值透傳問題

new Promise(resolve => {
  resolve(8)
})
  .then()
  .then()
  .then((value) => console.log(value))

從上面的 Promise 使用示例代碼中,咱們會發現一個場景:在Promise 鏈式調用中,當中間的 then 方法沒有接收到回調函數時,後面的 then 方法接收的回調函數仍可以獲取到前面傳遞的值。那麼這裏就須要then 方法在接收的回調函數做以下操做,即若是沒有傳入成功/失敗回調函數時,默認的回調函數須要將接收的值返回給下一個 Promise 實例對象。下面是對應的實現代碼:

then(onResolved, onRejected) {
    let self = this;
    let promise2;

    // 增長值的透傳功能
    onResolved =
      typeof onResolved === 'function'
        ? onResolved
        : function(value) {
            return value;
          };
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : function(reason) {
            throw reason;
          };
    ......
}

結束語

到這裏一個基本的 Promise 已經實現了,下面是完整的 Promise 代碼實現。經歷了整個過程,相信讀者對 Promise 的理解會更加深刻了。

class Promise {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }

    this.value = undefined; // Promise的值
    this.status = 'pending'; // Promise當前的狀態
    this.onResolvedCallback = []; // Promise resolve時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面
    this.onRejectedCallback = []; // Promise reject時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面

    const resolve = value => {
      if (this.status === 'pending') {
        this.status = 'resolved';
        this.value = value;
        for (let i = 0; i < this.onResolvedCallback.length; i++) {
          this.onResolvedCallback[i](value);
        }
      }
    };

    const reject = reason => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.value = reason;
        for (let i = 0; i < this.onRejectedCallback.length; i++) {
          this.onRejectedCallback[i](reason);
        }
      }
    };

    try {
      // 考慮到執行executor的過程當中有可能出錯,因此咱們用try/catch塊給包起來,而且在出錯後以catch到的值reject掉這個Promise
      executor(resolve, reject); // 執行executor
    } catch (e) {
      reject(e);
    }
  }

  // then方法接收兩個參數,onResolved,onRejected,分別爲Promise成功或失敗後的回調
  then(onResolved, onRejected) {
    let self = this;
    let promise2;

    // 增長值的透傳功能
    onResolved =
      typeof onResolved === 'function'
        ? onResolved
        : function(value) {
            return value;
          };
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : function(reason) {
            throw reason;
          };

    if (self.status === 'resolved') {
      // 若是promise1(此處即爲this/self)的狀態已經肯定而且是resolved,咱們調用onResolved
      // 由於考慮到有可能throw,因此咱們將其包在try/catch塊裏
      return (promise2 = new Promise(function(resolve, reject) {
        try {
          let x = onResolved(self.value);
          if (x instanceof Promise) {
            // 若是onResolved的返回值是一個Promise對象,直接取它的結果作爲promise2的結果
            x.then(resolve, reject);
          } else {
            resolve(x); // 不然,以它的返回值作爲promise2的結果
          }
        } catch (e) {
          reject(e); // 若是出錯,以捕獲到的錯誤作爲promise2的結果
        }
      }));
    }

    // 此處與前一個if塊的邏輯幾乎相同,區別在於所調用的是onRejected函數,就再也不作過多解釋
    if (self.status === 'rejected') {
      return (promise2 = new Promise(function(resolve, reject) {
        try {
          let x = onRejected(self.value);
          if (x instanceof Promise) {
            x.then(resolve, reject);
          } else {
            reject(x); // 不然,以它的返回值作爲promise2的結果
          }
        } catch (e) {
          reject(e);
        }
      }));
    }

    if (self.status === 'pending') {
      // 若是當前的Promise還處於pending狀態,咱們並不能肯定調用onResolved仍是onRejected,
      // 只能等到Promise的狀態肯定後,才能確實如何處理。
      // 因此咱們須要把咱們的**兩種狀況**的處理邏輯作爲callback放入promise1(此處即this/self)的回調隊列裏
      // 邏輯自己跟第一個if塊內的幾乎一致,此處不作過多解釋
      return (promise2 = new Promise(function(resolve, reject) {
        self.onResolvedCallback.push(function(value) {
          try {
            let x = onResolved(value);
            if (x instanceof Promise) {
              x.then(resolve, reject);
            } else {
              resolve(x);
            }
          } catch (e) {
            reject(e);
          }
        });

        self.onRejectedCallback.push(function(reason) {
          try {
            let x = onRejected(reason);
            if (x instanceof Promise) {
              x.then(resolve, reject);
            } else {
              reject(x);
            }
          } catch (e) {
            reject(e);
          }
        });
      }));
    }
  }
}

參考資料

相關文章
相關標籤/搜索