拒绝死记硬背!理解resolvePromise,三步实现符规范的Promise。

2,075 阅读12分钟

Step1:从构造函数开始

考虑以下三种场景,一般我们不会直接resolve,而是在Promise的构造函数执行异步任务,在异步任务回调时再执行resolve或者reject。又或者,在构造构造函数中通过throw抛出了异常。

// call resolve
new MyPromise((resolve, reject) => {
  resolve("helloworld");
});

// call reject
new MyPromise((resolve, reject) => {
  reject("111");
});

new MyPromise((resolve, reject) => {
  throw new Error("test try catch");
});

上面三个Promise的状态分别从Pending转移到Fulfilled、Rejected、Rejected。

因此构造函数的逻辑就是:

  1. 接收一个函数excutor作为参数
  2. 初始化Pending状态
  3. 通过try...catch包裹excutor的执行,执行excutor时传入resolvereject方法。
  4. resolvereject方法修改Promise的状态

因此,根据ES6的class语法,很容易写出这样的代码:

const PromiseStatus = {
  Pending: "PENDING",
  FulFilled: "FULFILLED",
  Rejected: "REJECTED"
};

class MyPromise {
  constructor(excutor) {
    this.status = PromiseStatus.Pending;
    this.value = undefined;
    this.reason = undefined;

    const resolve = (value) => {
      setTimeout(() => {
        if (this.status === PromiseStatus.Pending) {
          this.status = PromiseStatus.FulFilled;
          // 其他逻辑
        } 
      });
    };
    const reject = (reason) => {
      setTimeout(() => {
        if (this.status === PromiseStatus.Pending) {
          this.status = PromiseStatus.Rejected;
          // 其他逻辑
        } 
      });
    };

    try {
      excutor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
}

这样的话,Step1就完成了。目前是可以通过构造函数生成一个Promise实例,但没有then方法,resolvereject之后也没有执行其他逻辑。别急,我们一步一步来。

Step2: 简单的then方法

再来回顾一下我们一般怎么使用then方法?通常,我们会通过Promise去执行一个异步操作,然后通过then(callback)处理Promise返回的值。另外,还经常使用then的链式调用。再者,就是一个Promise可能会绑定多个then方法。

// 链式调用
new MyPromise((resolve) => {
  resolve("a");
})
  .then((val) => {
    console.log(val);
    return val + "b";
  })
  .then((val) => {
    console.log(val);
    return val + "c";
  })
  .then((val) => {
    console.log(val);
  });

// 同一个Promise,then可执行多次
const promise = new MyPromise((resolve) => {
  resolve("a");
});

promise.then((val) => console.log("1:", val));
promise.then((val) => console.log("2:", val));

除了异步任务正常的逻辑,也不要忘了then方法是可以传入第二个参数的,它也是一个方法,接收一个参数为据因,当Promise执行异常时,会把据因传到这个方法中。

new MyPromise((resolve) => {
  resolve("a");
})
  .then((val) => {
    throw new Error(val);
  })
  .then(
    (val) => {
      console.log("not run");
      return "not run";
    },
    (error) => {
      return "handle error";
    }
  )
  .then((val) => {
    console.log(val);
  });

那么,我们来总结一下实现then方法的几个点:

  1. 支持链式调用,支持值透传
  2. 同一个Promise,then可执行多次,当Promise的状态从Pending转化为Fulfilled或者Rejected时,依绑定的次序执行对应的回调。

在此之前,我们先定义一下then方法:

then(onFulfilled, onRejected): Promise<any>;

onFulfilled(x: any): any;

onRejected(x: any): any;

链式调用

调用then时,两个参数都是可选的,这时候,为了透传值,我们需要判断一下传入的两个参数是否为函数,如果不是,需要给它一个默认的处理方法:

  then(onFulfilled, onRejected) {
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };
  }

onFulfilled的默认处理方式就是直接透传value,而onRejected就是继续把据因往外抛出。

链式调用实际上就是在每次执行then,都会返回一个新的Promise,那么我们在then的最后实例化一个promise2进行返回即可:

  then(onFulfilled, onRejected) {
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    const promise2 = new MyPromise((resolve, reject) => {
        try {
            const x = onFulfilled(this.value);
            resolve(x);
        } catch(error) {
            reject(error);
        }
    });

    return promise2;
  }

then支持多次调用

同一个Promise可以多次调用then去绑定不同的回调,那么肯定是需要一个数组去存储onFulfilledonRejected

对于then方法的执行,分为三种情况:

  1. Promise的状态为Pending,那么执行then(onFulfilled, onRejected)时把onFulfilledonRejected方法分别放入数组中,待Promise的状态转为FulfilledRejected时,再依次执行。
  2. Promise的状态为Fulfilled,那么直接执行onFulfilled方法。
  3. Promise的状态为Rejected,那么直接执行onRejected方法。

基于以上的情况,首先我们增加两个数组去存储then方法传入的参数:

this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];

resolvereject是转化Promise状态的方法,因此,在执行这两个参数时,分别遍历这两个数组,以此执行里面的回调函数,并且传入值或者据因,此时,构造函数应该变成了这个样子:

  constructor(excutor) {
    this.status = PromiseStatus.Pending;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;

    // 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
    const resolve = (value) => {
      // promise 的状态只能更改一次
      if (this.status === PromiseStatus.Pending) {
        this.status = PromiseStatus.FulFilled;
        this.value = value;
        setTimeout(() => {
          this.onFulfilledCallbacks.forEach((callback) => {
            callback(value);
          });
        });
      }
    };

    // 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
    const reject = (reason) => {
      // promise 的状态只能更改一次
      if (this.status === PromiseStatus.Pending) {
        this.status = PromiseStatus.Rejected;
        this.reason = reason;
        setTimeout(() => {
          this.onRejectedCallbacks.forEach((callback) => {
            callback(reason);
          });
        });
      }
    };

    try {
      excutor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

然后,基于执行then方法时的不同状态,我们在promise2的构造函数中添加以下逻辑,以区分不同状态执行不同的操作:

const promise2 = new MyPromise((resolve, reject) => {
  _resolve = resolve;
  _reject = reject;

  if (this.status === PromiseStatus.Pending) {
    this.onFulfilledCallbacks.push(onFulfilledCallback);
    this.onRejectedCallbacks.push(onRejectedCallback);
  }

  if (this.status === PromiseStatus.FulFilled) {
    setTimeout(() => onFulfilledCallback(this.value));
  }

  if (this.status === PromiseStatus.Rejected) {
    setTimeout(() => onRejectedCallback(this.reason));
  }
});

return promise2;

你可能会注意到:onFulfilledCallbackonRejectedCallback是什么东西?它们是基于onFulfilledonRejected的封装,为什么需要这个封装?我们来看以下这个例子:

new Promise((resolve, reject) => {
  reject("a");
})
  .then(
    (val) => {
      console.log("not run");
      return "handle then";
    },
    (error) => {
      console.log('error:', error);
      return "handle error";
    }
  )
  .then((val) => {
    console.log(val);
  });
// error: a
// handler error

可以看到,onRejected处理完之后,then方法所返回的Promise的状态是Fulfilled,所以最后一个then输出了handle error的逻辑。实际上,只有onFulfilledonRejected抛出了错误,返回的Promise才从Pending转化到Rejected,否则,会转化到Fulfilled状态。因此,onFulfilledCallbackonRejectedCallback的实现如下:

    let _resolve;
    let _reject;

    const onFulfilledCallback = (value) => {
      try {
        const x = onFulfilled(value);
        _resolve(x);
      } catch (error) {
        _reject(error);
      }
    };

    const onRejectedCallback = (value) => {
      try {
        const x = onRejected(value);
        _resolve(x);
      } catch (error) {
        _reject(error);
      }
    };

_resolve_reject方法在promise2实例化时得到赋值,因此,目前实现的Promise完整代码应该是这样的:

class MyPromise {
  constructor(excutor) {
    this.status = PromiseStatus.Pending;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;

    // 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
    const resolve = (value) => {
      // promise 的状态只能更改一次
      if (this.status === PromiseStatus.Pending) {
        this.status = PromiseStatus.FulFilled;
        this.value = value;
        setTimeout(() => {
          this.onFulfilledCallbacks.forEach((callback) => {
            callback(value);
          });
        });
      }
    };

    // 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
    const reject = (reason) => {
      // promise 的状态只能更改一次
      if (this.status === PromiseStatus.Pending) {
        this.status = PromiseStatus.Rejected;
        this.reason = reason;
        setTimeout(() => {
          this.onRejectedCallbacks.forEach((callback) => {
            callback(reason);
          });
        });
      }
    };

    try {
      excutor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    let _resolve;
    let _reject;

    const onFulfilledCallback = (value) => {
      try {
        const x = onFulfilled(value);
        _resolve(x);
      } catch (error) {
        _reject(error);
      }
    };

    const onRejectedCallback = (value) => {
      try {
        const x = onRejected(value);
        _resolve(x);
      } catch (error) {
        _reject(error);
      }
    };

    const promise2 = new MyPromise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;

      if (this.status === PromiseStatus.Pending) {
        this.onFulfilledCallbacks.push(onFulfilledCallback);
        this.onRejectedCallbacks.push(onRejectedCallback);
      }

      if (this.status === PromiseStatus.FulFilled) {
        setTimeout(() => onFulfilledCallback(this.value));
      }

      if (this.status === PromiseStatus.Rejected) {
        setTimeout(() => onRejectedCallback(this.reason));
      }
    });

    return promise2;
  }
}

再次执行测试代码,结果和预期一致:

new MyPromise((resolve) => {
  resolve("a");
})
  .then((val) => {
    console.log(val);
    return val + "b";
  })
  .then((val) => {
    console.log(val);
    return val + "c";
  })
  .then((val) => {
    console.log(val);
  });
// a
// ab
// abc

const promise = new MyPromise((resolve) => {
  resolve("a");
});

promise.then((val) => console.log("1:", val));
promise.then((val) => console.log("2:", val));

// 1:a
// 2:a

但是,如果我们在then中返回一个Promise,又或者在resolve中返回一个Promise,那么,输出的结果就和预期不一致了:

new MyPromise((resolve) => {
  resolve("a");
})
  .then((val) => {
    return new MyPromise((resolve) => resolve(val));
  })
  .then((val) => {
    console.log("is promise, not a:", val);
  });

预期的结果应该是is a,但是我们目前却得到了is promise, not a的输出。主要是因为我们目前实现的Promise并不支持resolvePromise的过程,这部分是实现符合Promise A+规范的Promise中最难以理解的部分,接下来我们就来细说如何理解resolvePromise,而不是死记硬背。

Step3: 实现resolvePromise

理解为什么有resolvePromise

在写resolvePromise之前,我们需要理解为什么需要这个过程。从上面的例子来看,如果我们从then中显式返回一个Promise,那么,由于原本我们的then方法就返回一个Promise对象,如果不处理,那么就会造成,返回的结果是Promise的嵌套,所以才会打印is promise, not a的结果。

从PromiseA+的规范上来说就是:假设then返回的值为x,那么,x有可能是一个Promise对象,或者是一个thenable对象(拥有then方法的对象或函数)。因此需要通过resolvePromise去获得x的最终状态,或者说最终值。

先来看下这个例子:

new Promise((resolve) => {
  resolve();
})
  .then(() => {
    // 做了一堆逻辑
    return new Promise((resolve) => {
      // 做了一堆逻辑
      setTimeout(() => {
        resolve("final");
      }, 1000);
    });
  })
  .then((val) => {
    console.log("val:", val);
  });

第一个then方法中onFulfilled方法显式返回了一个Promise对象,我们把它称为pA。但是,我们实现then方法时知道,then方法会返回一个Promise对象,我们成为pB。此时pA和pB的状态应该是这样的:pB(pA)。

如果不通过resolvePromise处理,假如我们需要在第二个then中继续处理pA的状态,那么就会出现“嵌套Promise"的存在:

new Promise((resolve) => {
  resolve();
})
  .then(() => {
    // 做了一堆逻辑
    return new Promise((resolve) => {
      // 做了一堆逻辑
      setTimeout(() => {
        resolve("final");
      }, 1000);
    });
  })
  .then((pA) => {
    pA.then(() => {
      return new Promise((resolve) => {
        // 做了一堆逻辑
        setTimeout(() => {
          resolve("final");
        }, 1000);
      });
    }).then((pA2) => {
      pA2.then(() => {
        return new Promise((resolve) => {
          // 做了一堆逻辑
          setTimeout(() => {
            resolve("final");
          }, 1000);
        });
      }).then(pA3 => {
        pA3.then(() => {
          return new Promise((resolve) => {
            // 做了一堆逻辑
            setTimeout(() => {
              resolve("final");
            }, 1000);
          });
      })
    }).catch(() => {
      console.log('error');
    })
  })
  .catch(() => {
    console.log('error');
  });


假设我们需要依次读取文件,每个文件都依赖上一个文件的内容,那么Promise的链式调用就会变成恐怖的“死亡回调”:

readFile('./file1.txt')
  .then((val) => {
    return readFile(val);
  })
  .then(promise => {
    promise.then(val => {
      return readFile(val);
    }).then(promise => {
      promise.then(val => {
        return readFile(val);
      }).then(promise => {
        promise.then(val => {
          console.log('套不下去了。。。。');
        });
      });
    });
  });

这样看上去实际上是不必要的,因为如果我们加上异常处理的逻辑,这跟以前的死亡嵌套并没有任何区别。而我的理解是:resolvePromise解决了这种问题。

执行then方法返回一个Promise,记为promiseA,如果onFulfilled方法或者onRejected方法返回一个Promise,记为promiseB,那么,promiseA的状态取决于promiseB,也就是说如果promiseB的状态为fulfilled,那么promiseA的状态也为fullfiled,如果promiseB的状态为rejected,那么promiseA的状态也为rejected。同时,promiseB的最终值也为promiseA的最终值。

简单来说就是:promiseA的状态控制转移到了promiseB。从另一个方面理解就是:promiseB中的过程被resolvePromise“解构”了。假设最终值为Y,pX为Promise,那么:

Y = resolvePromise(pA(pB(pC(pD(pE(Y))))))

这样的话,上面的例子就可以变成:

readFile('./file1.txt')
  .then(val => readFile(val))
  .then(val => readFile(val))
  .then(val => readFile(val))
  .then(val => readFile(val))
  .then(val => readFile(val))
  .then(val => readFile(val))
  .then(val => readFile(val))

一目了然多清晰啊!好了,说了那么多,开始实现resolvePromise了。

实现 resolvePromise

resolvePromise方法需要传入四个参数:promisexresolvereject,其中promisethen方法返回的Promise实例,resolvereject为该Promise实例的方法,x为执行onFulfilled或者onRejected的返回值。

resolvePromise主要处理以下三点:

  1. 禁止循环调用
  2. 如果 x 是一个Promise实例
  3. 如果 x 是一个 thenable 对象

首先,禁止循环调用,也就是判断promisex是否为同一个对象:

const resolvePromise = (promise, x, resolve, reject) => {
  // 禁止循环调用
  if (promise === x) {
    reject(new TypeError("禁止循环调用"));
  }
};

然后就是判断是否为Promise实例,如果是Promise实例并且正处于Pending状态,那么等待x的状态发生变化,得到这个Promise执行后的值y。因为y也有可能是thenable对象或者Promise实例,因此,需要对y进行resolvePromise的调用。如果Promise 的状态已经是fulfilled或者rejected,那么直接调用x.then即可。

const resolvePromise = (promise, x, resolve, reject) => {
  // 禁止循环调用
  if (promise === x) {
    reject(new TypeError("禁止循环调用"));
  }

  // 如果 x 是 Promise 实例
  if (x instanceof MyPromise) {
    // 如果 x 的状态为 Pending,那么直到 x 为 Fulfilled 或 Rejected 才调用 resolve
    if (x.status === PromiseStatus.Pending) {
      x.then(
        (y) => {
          // 进一步 resolvePromise 是因为 y 也有可能是个 Promise 实例 / thenable 对象
          resolvePromise(promise, y, resolve, reject);
        },
        (r) => {
          reject(r);
        }
      );
    } else {
      x.then(resolve, reject);
    }
  }
};

然后继续判断x是否为thenable对象,如果是,则以x为x.then方法的this调用,得到值y,跟前面Promise的处理方式一样,y是有可能为Promise实例或者thenable对象,因此也需要进行resolvePromise的处理。如果x不为thenable对象,那么直接resolve(x)即可。

主要注意的是,处理thenable对象和处理Promise对象的方式有点像,但是,因为Promise实例的状态只要一切换就不会再执行,因此不需要担心重复执行的问题。但是thenable对象并没有这个限制,因此还需要添加一个辅助变量判断是否已经执行过x.then,如果已经执行过,则忽略。

const resolvePromise = (promise, x, resolve, reject) => {
  // 禁止循环调用
  if (promise === x) {
    reject(new TypeError("禁止循环调用"));
  }

  // 如果 x 是 Promise 实例
  if (x instanceof MyPromise) {
    // 如果 x 的状态为 Pending,那么直到 x 为 Fulfilled 或 Rejected 才调用 resolve
    if (x.status === PromiseStatus.Pending) {
      x.then(
        (y) => {
          // 进一步 resolvePromise 是因为 y 也有可能是个 Promise 实例 / thenable 对象
          resolvePromise(promise, y, resolve, reject);
        },
        (r) => {
          reject(r);
        }
      );
    } else {
      x.then(resolve, reject);
    }
  } else {
    // x 不是 Promise 实例

    // x 是函数或对象
    if (typeof x === "function" || (x !== null && typeof x === "object")) {
      let called = false;
      try {
        const then = x.then;
        // 确保只执行一次
        if (typeof then === "function") {
          then.call(
            x,
            (y) => {
              if (called) {
                return;
              }
              called = true;
              resolvePromise(promise, y, resolve, reject);
            },
            (r) => {
              if (called) {
                return;
              }
              called = true;
              reject(r);
            }
          );
        } else {
          // 是一个对象/函数,但不是thenable,直接resolve即可
          resolve(x);
        }
      } catch (error) {
        if (called) {
          return;
        }
        called = true;
        reject(error);
      }
    } else {
      // 不是Promise、thenable对象,直接resolve即可
      resolve(x);
    }
  }
};

就这样,完整的resolvePromise也就执行完毕。

Final: 最终实现,并通过测试

接合Step1Step2Step3,最终实现的Promise代码如下:

const PromiseStatus = {
  Pending: "PENDING",
  FulFilled: "FULFILLED",
  Rejected: "REJECTED"
};

class MyPromise {
  constructor(excutor) {
    this.status = PromiseStatus.Pending;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;

    // 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
    const resolve = (value) => {
      // promise 的状态只能更改一次
      if (this.status === PromiseStatus.Pending) {
        setTimeout(() => {
          this.status = PromiseStatus.FulFilled;
          this.value = value;
          this.onFulfilledCallbacks.forEach((callback) => {
            callback(value);
          });
        });
      }
    };

    // 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
    const reject = (reason) => {
      // promise 的状态只能更改一次
      if (this.status === PromiseStatus.Pending) {
        setTimeout(() => {
          this.status = PromiseStatus.Rejected;
          this.reason = reason;
          this.onRejectedCallbacks.forEach((callback) => {
            callback(reason);
          });
        });
      }
    };

    try {
      excutor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    let promise2;
    let _resolve;
    let _reject;

    const resolvePromise = (promise, x, resolve, reject) => {
      // 禁止循环调用
      if (promise === x) {
        reject(new TypeError("禁止循环调用"));
      }

      // 如果 x 是 Promise 实例
      if (x instanceof MyPromise) {
        // 如果 x 的状态为 Pending,那么直到 x 为 Fulfilled 或 Rejected 才调用 resolve
        if (x.status === PromiseStatus.Pending) {
          x.then(
            (y) => {
              // 进一步 resolvePromise 是因为 y 也有可能是个 Promise 实例 / thenable 对象
              resolvePromise(promise, y, resolve, reject);
            },
            (r) => {
              reject(r);
            }
          );
        } else {
          x.then(resolve, reject);
        }
      } else {
        // x 不是 Promise 实例

        // x 是函数或对象
        if (typeof x === "function" || (x !== null && typeof x === "object")) {
          let called = false;
          try {
            const then = x.then;
            // 确保只执行一次
            if (typeof then === "function") {
              then.call(
                x,
                (y) => {
                  if (called) {
                    return;
                  }
                  called = true;
                  resolvePromise(promise, y, resolve, reject);
                },
                (r) => {
                  if (called) {
                    return;
                  }
                  called = true;
                  reject(r);
                }
              );
            } else {
              resolve(x);
            }
          } catch (error) {
            if (called) {
              return;
            }
            called = true;
            reject(error);
          }
        } else {
          resolve(x);
        }
      }
    };

    const onFulfilledCallback = (value) => {
      try {
        const x = onFulfilled(value);
        resolvePromise(promise2, x, _resolve, _reject);
      } catch (error) {
        _reject(error);
      }
    };

    const onRejectedCallback = (value) => {
      try {
        const x = onRejected(value);
        resolvePromise(promise2, x, _resolve, _reject);
      } catch (error) {
        _reject(error);
      }
    };

    promise2 = new MyPromise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;

      if (this.status === PromiseStatus.Pending) {
        this.onFulfilledCallbacks.push(onFulfilledCallback);
        this.onRejectedCallbacks.push(onRejectedCallback);
      }

      if (this.status === PromiseStatus.FulFilled) {
        setTimeout(() => onFulfilledCallback(this.value));
      }

      if (this.status === PromiseStatus.Rejected) {
        setTimeout(() => onRejectedCallback(this.reason));
      }
    });

    return promise2;
  }
}

最后通过promises-aplus-tests这个测试工具进行测试,结果表明:通过872个用例,符合Promise A+规范!