按部就班實現Promise

使用JavaScript按部就班實現一個簡單的Promise,支持異步和then鏈式調用。
翻譯並整理自 Medium: Implementing a simple Promise in Javascript - by Zhi Sun

前言

在前端面試和平常開發中,常常會接觸到Promise。而且在現現在的不少面試中,也會常常被要求手寫Promise。javascript

接下來,將使用JavaScript按部就班實現一個簡單的Promise,支持異步和then鏈式調用。前端

分析Promise

Promise對象用於表示一個異步操做的最終完成 (或失敗)及其結果值,經常使用來實現異步操做。java

Promise狀態

Promise有三種狀態:面試

  • pending數組

    初始狀態promise

  • fulfilled異步

    執行成功後的狀態函數

  • rejected測試

    執行失敗後的狀態this

Promise狀態只能由pending改變爲fulfilled或者由pending改變爲rejected,Promise狀態改變的這一過程被稱爲settled,而且,狀態一旦改變,後續就不會再次被改變。

Promise構造函數中的參數

Promise構造函數接收一個函數參數executor,該函數接收兩個參數:

  • resolve
  • reject

執行resolve會將Promise狀態由pending改變爲fulfilled,並觸發then方法中的成功回調函數onFulfilled

執行reject會將Promise狀態由pending改變爲rejected,並觸發then方法中的失敗回調函數onRejected

then方法中的回調函數參數

then方法接收兩個參數:

  • onFulfilled

    成功回調函數,接收一個參數,即resolve函數中傳入的值

  • onRejected

    失敗回調函數,接收一個參數,即reject函數中傳入的值

若是Promise狀態變爲fulfilled,就會執行成功回調函數onFulfilled;若是Promise狀態變爲rejected,就會執行失敗回調函數onRejected

實現Promise

基礎Promise

首先,constructor接收一個函數executor,該函數又接收兩個參數,分別是resolvereject函數。

所以,須要在constructor中建立resolvereject函數,並傳入executor函數中。

class MyPromise {
  constructor(executor) {
    const resolve = (value) => {};

    const reject = (reason) => {};

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
}

其次,Promise會根據狀態,執行對應的回調函數。最開始的狀態爲pending,當resolve時,狀態由pending變爲fulfilled;當reject時,狀態由pending變爲rejected。而且,一旦狀態變動後,就不會再次變動。

class MyPromise {
  constructor(executor) {
    this.state = 'pending';

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
}

Promise狀態變動後,會觸發then方法中對應的回調函數。若是狀態由pending變爲fulfilled,則會觸發成功回調函數,若是狀態由pending變爲rejected,則會觸發失敗回調函數。

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = null;

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.value = reason;
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    }

    if (this.state === 'rejected') {
      onRejected(this.value);
    }
  }
}

接下來能夠寫點測試代碼測試一下功能

const p1 = new MyPromise((resolve, reject) => resolve('resolved'));
p1.then(
  (res) => console.log(res), // resolved
  (err) => console.log(err)
);

const p2 = new MyPromise((resolve, reject) => reject('rejected'));
p2.then(
  (res) => console.log(res),
  (err) => console.log(err) // rejected
);

可是,若是用如下代碼測試,會發現什麼也沒有輸出。

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('resolved'), 1000);
});

p1.then(
  (res) => console.log(res),
  (err) => console.log(err)
);

const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => reject('rejected'), 1000);
});

p2.then(
  (res) => console.log(res),
  (err) => console.log(err)
);

這是由於在調用then方法時,Promise仍處於pending狀態。onFulfilledonRejected回調函數都沒有被執行。

所以,接下來須要支持異步。

支持異步的Promise

爲了支持異步,須要先保存onFulfilledonRejected回調函數,一旦Promise狀態變化,馬上執行對應的回調函數。

⚠:這裏有個細節須要注意,即onFulfilledCallbacksonRejectedCallbacks是數組,由於Promise可能會被調用屢次,所以會存在多個回調函數。

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;

        this.onFulfilledCallbacks.forEach((fn) => fn(value));
      }
    };

    const reject = (value) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.value = value;

        this.onRejectedCallbacks.forEach((fn) => fn(value));
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.state === 'pending') {
      this.onFulfilledCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
    }

    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    }

    if (this.state === 'rejected') {
      onRejected(this.value);
    }
  }
}

接下來能夠用以前的測試代碼測試一下功能

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('resolved'), 1000);
});

p1.then(
  (res) => console.log(res), // resolved
  (err) => console.log(err)
);

const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => reject('rejected'), 1000);
});

p2.then(
  (res) => console.log(res),
  (err) => console.log(err) // rejected
);

可是若是用如下代碼測試,會發現報錯了。

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('resolved'), 1000);
});

p1.then(
  (res) => console.log(res),
  (err) => console.log(err)
).then(
  (res) => console.log(res),
  (err) => console.log(err)
); // Uncaught TypeError: Cannot read property 'then' of undefined

這是由於第一個then方法並無返回任何值,然而卻又連續調用了then方法。

所以,接下來須要實現then鏈式調用。

支持then鏈式調用的Promise

要想支持then鏈式調用,then方法須要返回一個新的Promise。

所以,須要改造一下then方法,返回一個新的Promise,等上一個Promise的onFulfilledonRejected回調函數執行完成後,再執行新的Promise的resolvereject函數。

class MyPromise {
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          try {
            const fulfilledFromLastPromise = onFulfilled(this.value);
            resolve(fulfilledFromLastPromise);
          } catch (err) {
            reject(err);
          }
        });

        this.onRejectedCallbacks.push(() => {
          try {
            const rejectedFromLastPromise = onRejected(this.value);
            reject(rejectedFromLastPromise);
          } catch (err) {
            reject(err);
          }
        });
      }

      if (this.state === 'fulfilled') {
        try {
          const fulfilledFromLastPromise = onFulfilled(this.value);
          resolve(fulfilledFromLastPromise);
        } catch (err) {
          reject(err);
        }
      }

      if (this.state === 'rejected') {
        try {
          const rejectedFromLastPromise = onRejected(this.value);
          reject(rejectedFromLastPromise);
        } catch (err) {
          reject(err);
        }
      }
    });
  }
}

接下來能夠用如下代碼測試一下功能

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('resolved'), 1000);
});

p1.then(
  (res) => {
    console.log(res); // resolved
    return res;
  },
  (err) => console.log(err)
).then(
  (res) => console.log(res), // resolved
  (err) => console.log(err)
);

const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => reject('rejected'), 1000);
});

p2.then(
  (res) => console.log(res),
  (err) => {
    console.log(err); // rejected
    throw new Error('rejected');
  }
).then(
  (res) => console.log(res),
  (err) => console.log(err) // Error: rejected
);

可是,若是改用如下代碼測試,會發現第二個then方法中的成功回調函數並無按預期輸出(‘resolved’),而是輸出了上一個then方法的onFulfilled回調函數中返回的Promise。

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('resolved'), 1000);
});

p1.then(
  (res) => {
    console.log(res); // resolved
        return new MyPromise((resolve, reject) => {
      setTimeout(() => resolve('resolved'), 1000);
    })
  },
  (err) => console.log(err)
).then(
  (res) => console.log(res), // MyPromise {state: "pending"}
  (err) => console.log(err)
);

這是由於onFulfilled/onRejected回調函數執行完以後,只是簡單的將onFulfilled/onRejected執行完返回的值傳入resolve/reject函數中執行,並無考慮onFulfilled/onRejected執行完會返回一個新的Promise這種狀況,因此第二次then方法的成功回調函數中直接輸出了上一次then方法的成功回調函數中返回的Promise。所以,接下來須要解決這個問題。

首先,能夠將以上測試代碼改爲另外一種寫法,方便梳理思路。

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('resolved'), 1000);
});

const p2 = p1.then(
  (res) => {
    console.log(res);
    const p3 = new MyPromise((resolve, reject) => {
      setTimeout(() => resolve('resolved'), 1000);
    });

    return p3;
  },
  (err) => console.log(err)
);

p2.then(
  (res) => console.log(res),
  (err) => console.log(err)
);

能夠看到,一共有三個Promise:

  • 第一個Promise

    即經過new構造出來的p1

  • 第二個Promise

    即經過調用then方法返回的p2

  • 第三個Promise

    即在p1.then方法的成功回調函數參數中返回的p3

如今的問題是,調用p2的then方法時,p3還處於pending狀態。

要想實現p2.then方法中的回調函數能正確輸出p3中resolve/reject以後的值,須要先等p3狀態變化後再將變化後的值傳入p2中的resolve/reject中便可。換句話說,三個Promise狀態變化的前後順序應該是p1 --> p3 --> p2。

class MyPromise {
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          try {
            const fulfilledFromLastPromise = onFulfilled(this.value);

            if (fulfilledFromLastPromise instanceof MyPromise) {
              fulfilledFromLastPromise.then(resolve, reject);
            } else {
              resolve(fulfilledFromLastPromise);
            }
          } catch (err) {
            reject(err);
          }
        });

        this.onRejectedCallbacks.push(() => {
          try {
            const rejectedFromLastPromise = onRejected(this.value);

            if (rejectedFromLastPromise instanceof MyPromise) {
              rejectedFromLastPromise.then(resolve, reject);
            } else {
              reject(rejectedFromLastPromise);
            }
          } catch (err) {
            reject(err);
          }
        });
      }

      if (this.state === 'fulfilled') {
        try {
          const fulfilledFromLastPromise = onFulfilled(this.value);

          if (fulfilledFromLastPromise instanceof MyPromise) {
            fulfilledFromLastPromise.then(resolve, reject);
          } else {
            resolve(fulfilledFromLastPromise);
          }
        } catch (err) {
          reject(err);
        }
      }

      if (this.state === 'rejected') {
        try {
          const rejectedFromLastPromise = onRejected(this.value);

          if (rejectedFromLastPromise instanceof MyPromise) {
            rejectedFromLastPromise.then(resolve, reject);
          } else {
            reject(rejectedFromLastPromise);
          }
        } catch (err) {
          reject(err);
        }
      }
    });
  }
}

最終版Promise

最後,一個簡單的Promise就完成了,支持異步和then鏈式調用。完整代碼以下:

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach((fn) => fn(value));
      }
    };

    const reject = (value) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.value = value;
        this.onRejectedCallbacks.forEach((fn) => fn(value));
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          try {
            const fulfilledFromLastPromise = onFulfilled(this.value);
            if (fulfilledFromLastPromise instanceof Promise) {
              fulfilledFromLastPromise.then(resolve, reject);
            } else {
              resolve(fulfilledFromLastPromise);
            }
          } catch (err) {
            reject(err);
          }
        });
        this.onRejectedCallbacks.push(() => {
          try {
            const rejectedFromLastPromise = onRejected(this.value);
            if (rejectedFromLastPromise instanceof Promise) {
              rejectedFromLastPromise.then(resolve, reject);
            } else {
              reject(rejectedFromLastPromise);
            }
          } catch (err) {
            reject(err);
          }
        });
      }

      if (this.state === 'fulfilled') {
        try {
          const fulfilledFromLastPromise = onFulfilled(this.value);
          if (fulfilledFromLastPromise instanceof Promise) {
            fulfilledFromLastPromise.then(resolve, reject);
          } else {
            resolve(fulfilledFromLastPromise);
          }
        } catch (err) {
          reject(err);
        }
      }

      if (this.state === 'rejected') {
        try {
          const rejectedFromLastPromise = onRejected(this.value);
          if (rejectedFromLastPromise instanceof Promise) {
            rejectedFromLastPromise.then(resolve, reject);
          } else {
            reject(rejectedFromLastPromise);
          }
        } catch (err) {
          reject(err);
        }
      }
    });
  }
}
相關文章
相關標籤/搜索