手写Promise

219 阅读6分钟

写了一年的Promise,今天终于是把测试跑通了,其它都不是难点,难点在于resolvePromise,这是所有里面最难的,当然我们抛开这个先不谈,先实现一些基本的特性.

1. Promise 的三个基本属性

Promise有三个基本属性

  1. value:Promise成功的值
  2. reason:Promise失败的理由
  3. state:状态,有pendingfulfilledrejected状态

所以我们可以添加三个属性值,以及关于State的三个状态的常量:

const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value; // 成功的值
      reason; // 失败的理由
      state = State.pending;
 }

2. Promise 的构造器

Promise的构造器传入参数是一个函数,并传入resolvereject函数,用来改变Promise对象的状态,所以我们可以得到如下代码:

const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this)); // 新增的代码
      } 
      
      resolve(){} // 新增的代码
      reject(){} // 新增的代码
 }

2. resolve和reject

Promise中调用这两个函数,分别会将Promise的状态改为fulfilledrejected,所以加入状态的改变:

const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this));
      } 
      
      resolve(value){
          this.value = value; // 新增的代码
          this.state = State.fulfilled; // 新增的代码
      }
      
      reject(reason){
         this.reason = reason; // 新增的代码
         this.state = State.rejected; // 新增的代码
      } 
 }

当然,Promise状态修改后,就不能再次修改,所以我们加入判断

const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this));
      } 
      
      resolve(value){
          if (this.state !== State.pending) return; // 新增的代码

          this.value = value; 
          this.state = State.fulfilled; 
      } 
      
      reject(reason){
         if (this.state !== State.pending) return; // 新增的代码
        
         this.reason = reason; 
         this.state = State.rejected;
      } 
 }

3. then函数

then函数返回一个新的Promise对象,参数是两个函数,第一个函数是前一个promise对象被Resolve时调用,第二个函数是前一个对象被Reject时调用,所以我们加上一个then方法,并返回一个新的Promise,并且调用它的ResolveReject

const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this));
      } 
      
      resolve(value){
          if (this.state !== State.pending) return;

          this.value = value; 
          this.state = State.fulfilled; 
      } 
      
      reject(reason){
         if (this.state !== State.pending) return;
        
         this.reason = reason; 
         this.state = State.rejected;
      } 
      
      // 新增代码
      then(onFulfilled, onRejected) {
        let onFulfilledFn;
        let onRejectedFn;
        
        const nextPromise = new MyPromise((resolve, reject) => {
          onFulfilledFn = function () {
              const result = onFulfilled(this.value)
              resolve(result); 
          };

          onRejectedFn = function () {
              const result = onRejected(this.reason);
              resolve(result);
          };
        });

        onFulfilledFn = onFulfilledFn.bind(this);
        onRejectedFn = onRejectedFn.bind(this);

        if (this.state === State.fulfilled) {
          onFulfilledFn();
        } else if (this.state === State.rejected) {
          onRejectedFn();
        }
        
        return nextPromise;
     }
 }

promise对象resolve或者reject的时候,then函数不是立即执行的,也就是说是异步的,所以,我们需要在回调外面套上queueMircrotask

     ...省略
      then(onFulfilled, onRejected) {
        let onFulfilledFn;
        let onRejectedFn;
        
        const nextPromise = new MyPromise((resolve, reject) => {
          onFulfilledFn = function () {
             queueMicrotask(()=>{
              const result = onFulfilled(this.value)
              resolve(result); 
             });
          };

          onRejectedFn = function () {
              queueMircotask(()=>{
               const result = onRejected(this.reason);
               resolve(result);
              })
          };
        });

        onFulfilledFn = onFulfilledFn.bind(this);
        onRejectedFn = onRejectedFn.bind(this);

        if (this.state === State.fulfilled) {
          onFulfilledFn();
        } else if (this.state === State.rejected) {
          onRejectedFn();
        }
        
        return nextPromise;
     }
    ...省略

但是此时我们执行这个例子

new MyPromise((resolve)=>{ setTimeout(()=>{ resolve(1) }) })
.then((res)=>{console.log(res)})

你会发现没有任何输出

image.png

这是因为刚才的then只能同步执行image.png

所以我们需要用数组来保存订阅,并在状态发生变更的时候执行数组中的函数:

...省略
class MyPromise {
  ...省略
  onFulfilledFnArray = [];
  onRejectedFnArray = [];
  ...省略
}
...省略

then变更为

...省略
 then(onFulfilled, onRejected) {
        let onFulfilledFn;
        let onRejectedFn;
        
        const nextPromise = new MyPromise((resolve, reject) => {
          onFulfilledFn = function () {
             queueMicrotask(()=>{
              const result = onFulfilled(this.value)
              resolve(result); 
             });
          };

          onRejectedFn = function () {
              queueMircotask(()=>{
               const result = onRejected(this.reason);
               resolve(result);
              })
          };
        });

        onFulfilledFn = onFulfilledFn.bind(this);
        onRejectedFn = onRejectedFn.bind(this);

        if (this.state === State.fulfilled) {
          onFulfilledFn();
        } else if (this.state === State.rejected) {
          onRejectedFn();
        } else {
          // 新增代码
          this.onFulfilledFnArray.push(onFulfilledFn);
          this.onRejectedFnArray.push(onRejectedFn);
        }

        return nextPromise;
     }
...省略

修改一下resolvereject

...省略
class MyPromise {
  ...省略
   resolve(value) {
        if (this.state !== State.pending) return;

        this.value = value;
        this.state = State.fulfilled;

        // 新增代码
        while (this.onFulfilledFnArray.length > 0) {
          this.onFulfilledFnArray.shift()(); // 不是立即执行
        }
  }

  reject(reason) {
        if (this.state !== State.pending) return;

        this.reason = reason;
        this.state = State.rejected;

        // 新增代码
        while (this.onRejectedFnArray.length > 0) {
          this.onRejectedFnArray.shift()(); // 不是立即执行
        }
  }

  ...省略
}
...省略

给出当前的完整代码:

const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      onFulfilledFnArray = [];
      onRejectedFnArray = [];
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this));
      } 
      
      resolve(value) {
        if (this.state !== State.pending) return;

        this.value = value;
        this.state = State.fulfilled;

        // 新增代码
        while (this.onFulfilledFnArray.length > 0) {
          this.onFulfilledFnArray.shift()(); // 不是立即执行
        }
      }

      reject(reason) {
        if (this.state !== State.pending) return;

        this.reason = reason;
        this.state = State.rejected;

        // 新增代码
        while (this.onRejectedFnArray.length > 0) {
          this.onRejectedFnArray.shift()(); // 不是立即执行
        }
      }
      
      then(onFulfilled, onRejected) {
        let onFulfilledFn;
        let onRejectedFn;
        
        const nextPromise = new MyPromise((resolve, reject) => {
          onFulfilledFn = function () {
             queueMicrotask(()=>{
              const result = onFulfilled(this.value)
              resolve(result); 
             });
          };

          onRejectedFn = function () {
              queueMircotask(()=>{
               const result = onRejected(this.reason);
               resolve(result);
              })
          };
        });

        onFulfilledFn = onFulfilledFn.bind(this);
        onRejectedFn = onRejectedFn.bind(this);

        if (this.state === State.fulfilled) {
          onFulfilledFn();
        } else if (this.state === State.rejected) {
          onRejectedFn();
        } else {
          // 新增代码
          this.onFulfilledFnArray.push(onFulfilledFn);
          this.onRejectedFnArray.push(onRejectedFn);
        }

        return nextPromise;
     }
 }

可以发现成功输出1

image.png

4. resolve传递值的规则

  1. resolve不能传递同一个Promise对象,否则会报错 这种情况一般发生在异步代码中:
const promise = new Promise((resolve, reject)=>{
   setTimeout(()=>{
       resolve(promise)
   })
})

image.png

所以我们需要写一个resolvePromise的函数,来过滤掉特殊情况

resolvePromise(value) {
   if (value === this) {
      this.reject(
        new TypeError(
          'Circular reference detected: promise and x are the same object',
        ),
      );
      return false;
    }  
}
  1. thenable对象 当遇到thenable对象时,会调用thenable对象的then,并将resolve的值作为promise的最终状态。

具体可查看 Promise - JavaScript | MDN (mozilla.org)

所以我们可以加入如下代码:

  resolvePromise(value) {
    let called = false;

    if (value === this) {
       // 省略
    } else if (value instanceof MyPromise) {
      try {
        value.then(
          x => {
            if (called) return;
            called = true;
            this.resolve(x);
          },
          y => {
            if (called) return;
            called = true;
            this.reject(y);
          },
        );
        return false;
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    } else if (value && typeof value === 'object') {
      try {
        const thenable = Reflect.get(value, 'then');
        if (typeof thenable === 'function') {
          try {
            thenable.call(
              value,
              x => {
                if (called) return;
                called = true;
                this.resolve(x);
              },
              y => {
                if (called) return;
                called = true;
                this.reject(y);
              },
            );
            return false;
          } catch (e) {
            if (called) return;
            this.reject(e);
            return false;
          }
        }
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    } else if (value && typeof value === 'function') {
      try {
        if (Reflect.has(value, 'then')) {
          return this.resolvePromise(Reflect.get(value, 'then'));
        }
        if (value) {
          try {
            value.call(
              value,
              x => {
                if (called) return;
                called = true;
                this.resolve(x);
              },
              y => {
                if (called) return;
                called = true;
                this.reject(y);
              },
            );
            return false;
          } catch (e) {
            if (called) return;
            this.reject(e);
            return false;
          }
        }
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    }

    return true;
  }

image.png

可以看到我们这里加入了called,这是为了防止反复调用rejectresolve, 例如:

Promise.resolve({
  then(resolve, reject) {
    resolve(1);
    reject(2); // 这次调用就直接跳过了
  },
});

最后修改一下resolve函数:

  resolve(value) {
    if (this.state !== State.pending) return;
    if (!this.resolvePromise(value)) return;  // 特殊对象,特殊解析

    this.value = value;
    this.state = State.fulfilled;

    while (this.onFulfilledFnArray.length > 0) {
      this.onFulfilledFnArray.shift()(); // 不是立即执行
    }
  }

5. 异常处理

现在我们需要为then函数加入错误处理。举个例子:

Promise.resolve().then(()=>{ throw 'error'; })

then报错时,对应promise对象状态会变成reject

image.png

所以我们需要在then中调用下个resolvereject之外套上try{} catch {}:

先给出完整代码:

...省略
  then(onFulfilled, onRejected) {
    let onFulfilledFn;
    let onRejectedFn;
    const nextPromise = new MyPromise((resolve, reject) => {
      onFulfilledFn = function () {
        queueMicrotask(() => {
          try {  
            if (typeof onFulfilled === 'function') {
              const result = onFulfilled(this.value);
              resolve(result);
            } else {
              resolve(this.value);
            }
          } catch (e) { // 新增 try catch
            reject(e);
          }
        });
      };

      onRejectedFn = function () {
        queueMicrotask(() => {
          try { // 新增 try catch
            if (onRejected) {
              if (typeof onRejected === 'function') {
                const result = onRejected(this.reason);
                resolve(result);
              } else {
                reject(this.reason);
              }
            } else {
              reject(this.reason);
            }
          } catch (e) { // 新增 try catch
            reject(e);
          }
        });
      };
    });

    onFulfilledFn = onFulfilledFn.bind(this);
    onRejectedFn = onRejectedFn.bind(this);

    if (this.state === State.fulfilled) {
      onFulfilledFn();
    } else if (this.state === State.rejected) {
      onRejectedFn();
    } else {
      this.onFulfilledFnArray.push(onFulfilledFn);
      this.onRejectedFnArray.push(onRejectedFn);
    }

    return nextPromise;
  }
...省略

还没想好怎么写,🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧

给出完整代码以及测试:

const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
  value; // 成功的值
  reason; // 失败的理由
  state = State.pending;
  onFulfilledFnArray = [];
  onRejectedFnArray = []; // 为了可以 let a = Promise; a.then(); a.then()

  constructor(exector) {
    exector?.(this.resolve.bind(this), this.reject.bind(this));
  }

  static resolve(value) {
    return new MyPromise(resolve => {
      resolve(value);
    });
  }

  static reject(value) {
    return new MyPromise((resolve, reject) => {
      reject(value);
    });
  }

  resolvePromise(value) {
    let called = false;

    if (value === this) {
      this.reject(
        new TypeError(
          'Circular reference detected: promise and x are the same object',
        ),
      );

      return false;
    } else if (value instanceof MyPromise) {
      try {
        value.then(
          x => {
            if (called) return;
            called = true;
            this.resolve(x);
          },
          y => {
            if (called) return;
            called = true;
            this.reject(y);
          },
        );
        return false;
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    } else if (value && typeof value === 'object') {
      try {
        const thenable = Reflect.get(value, 'then');
        if (typeof thenable === 'function') {
          try {
            thenable.call(
              value,
              x => {
                if (called) return;
                called = true;
                this.resolve(x);
              },
              y => {
                if (called) return;
                called = true;
                this.reject(y);
              },
            );
            return false;
          } catch (e) {
            if (called) return;
            this.reject(e);
            return false;
          }
        }
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    } else if (value && typeof value === 'function') {
      try {
        if (Reflect.has(value, 'then')) {
          return this.resolvePromise(Reflect.get(value, 'then'));
        }
        if (value) {
          try {
            value.call(
              value,
              x => {
                if (called) return;
                called = true;
                this.resolve(x);
              },
              y => {
                if (called) return;
                called = true;
                this.reject(y);
              },
            );
            return false;
          } catch (e) {
            if (called) return;
            this.reject(e);
            return false;
          }
        }
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    }

    return true;
  }

  resolve(value) {
    if (this.state !== State.pending) return;
    if (!this.resolvePromise(value)) return;

    this.value = value;
    this.state = State.fulfilled;

    while (this.onFulfilledFnArray.length > 0) {
      this.onFulfilledFnArray.shift()(); // 不是立即执行
    }
  }

  reject(reason) {
    if (this.state !== State.pending) return;

    this.reason = reason;
    this.state = State.rejected;

    while (this.onRejectedFnArray.length > 0) {
      this.onRejectedFnArray.shift()(); // 不是立即执行
    }
  }

  catch(onFulfilled) {
    return this.then(undefined, onFulfilled);
  }

  then(onFulfilled, onRejected) {
    let onFulfilledFn;
    let onRejectedFn;
    const nextPromise = new MyPromise((resolve, reject) => {
      onFulfilledFn = function () {
        queueMicrotask(() => {
          try {
            if (typeof onFulfilled === 'function') {
              const result = onFulfilled(this.value);
              resolve(result);
            } else {
              resolve(this.value);
            }
          } catch (e) {
            reject(e);
          }
        });
      };

      onRejectedFn = function () {
        queueMicrotask(() => {
          try {
            if (onRejected) {
              if (typeof onRejected === 'function') {
                const result = onRejected(this.reason);
                resolve(result);
              } else {
                reject(this.reason);
              }
            } else {
              reject(this.reason);
            }
          } catch (e) {
            reject(e);
          }
        });
      };
    });

    onFulfilledFn = onFulfilledFn.bind(this);
    onRejectedFn = onRejectedFn.bind(this);

    if (this.state === State.fulfilled) {
      onFulfilledFn();
    } else if (this.state === State.rejected) {
      onRejectedFn();
    } else {
      this.onFulfilledFnArray.push(onFulfilledFn);
      this.onRejectedFnArray.push(onRejectedFn);
    }

    return nextPromise;
  }
}

代码仓库以及测试: javascript/面试题/手写Promise/index.js at master · RadiumAg/javascript (github.com)