【JavaScript】promise实现原理解析

2023/01/29 10:12:27

观察者模式与 Promise A+ 规范

promise 可以看成是观察者模式的实现:then 收集依赖 -> 异步触发 resolve -> resolve 执行依赖

ES6 的 promise 实现遵循 Promise/A+规范open in new window,这里只总结两条规则:

  • Promise 本质是一个状态机,且状态只能为以下三种:Pending(等待态)、Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从 Pending -> FulfilledPending -> Rejected,状态变更不可逆。
  • then 方法接收两个可选参数,分别对应状态改变时触发的回调。then 方法返回一个 promise。then 方法可以被同一个 promise 调用多次。
class MyPromise {
  // Promise/A+规范的三种状态
  static PENDING = "pending";
  static FULFILLED = "fulfilled";
  static REJECTED = "rejected";

  constructor(executor) {
    this._value = undefined; // 记录最后一次then的值
    this._status = MyPromise.PENDING;
    this._resolveQueue = [];
    this._rejectQueue = [];

    const _resolve = (val) => {
      if (this._status !== MyPromise.PENDING) return;
      this._value = val;
      this._status = MyPromise.FULFILLED;
      while (this._resolveQueue.length) {
        const callback = this._resolveQueue.shift();
        callback();
      }
    };

    const _reject = (val) => {
      if (this._status !== MyPromise.PENDING) return;
      this._value = val;
      this._status = MyPromise.REJECTED;
      while (this._rejectQueue.length) {
        const callback = this._rejectQueue.shift();
        callback();
      }
    };

    executor(_resolve, _reject);
  }

  then(resolveFn, rejectFn) {
    this._resolveQueue.push(resolveFn);
    this._rejectQueue.push(rejectFn);
  }
}

这种实现方式可以让我们在 then 方法中获取到异步操作的返回值:

new MyPromise((resolve) => {
  setTimeout(() => {
    resolve(9);
  }, 500);
}).then(console.log);
// 500ms 后输出 9

then 的异步链式调用

promise 的 .then() 方法支持异步链式调用:

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

思考如何实现这种链式调用:

  • .then() 需要返回一个 promise,这样才能找到 then 方法,所以要把 then 方法的返回值包装成 promise。
  • .then() 需要按顺序执行,如果回调返回一个新的 promise,下一个 .then() 需要等到这个 promise 状态变更之后执行。
  • .then() 的回调需要拿到上一个 .then() 的返回值。

实现类似 promise 的异步链式调用中对这种封装思路进行了解析。

class MyPromise {
  // ...
  then(resolveFn, rejectFn) {
    return new MyPromise((resolve, reject) => {
      // 封装 resolveFn 回调的执行结果
      const fulfilledFn = () => {
        try {
          const res = resolveFn(this._value);
          if (res instanceof MyPromise) {
            res.then(resolve);
          } else {
            resolve(res);
          }
        } catch (error) {
          reject(error);
        }
      };
      this._resolveQueue.push(fulfilledFn);

      // 封装 rejectFn 回调的执行结果
      const rejectedFn = () => {
        try {
          const res = rejectFn(this._value);
          if (res instanceof MyPromise) {
            res.then(resolve, reject);
          } else {
            resolve(res);
          }
        } catch (err) {
          reject(err);
        }
      };
      this._rejectQueue.push(rejectedFn);
    });
  }
}

值穿透&状态已变更的情况&兼容同步任务

  1. 值穿透

    根据规范,如果 then 接收的参数不为 function,我们应该忽略它,并将上一个 then 的返回值传递到下一个 then 的回调中。

  2. 状态已变更的情况

    上面的实现中只在 resolve 之后开始执行回调,而在实际使用中会出现在 resolve 之后调用 then 的情况:Promise.resolve().then()

    对于状态已经变更为 fulfilledrejected的情况时,直接执行 then 回调。

  3. 兼容同步任务

    上面的实现中都默认 executor 中是异步方法,如果 executor 是同步方法,会存在一些问题。对于下面这个例子而言,期望的打印顺序是:1 4 2 3,实际打印顺序是:1 2 4 3

    为了兼容同步任务,我们给 resolve/reject 执行回调的操作包一个 setTimeout,让它异步执行。

    console.log(1);
    const a = new MyPromise((resolve) => {
      resolve(2);
    })
      .then(console.log)
      .then(() => {
        return new MyPromise((resolve) => {
          setTimeout(() => {
            resolve(3);
          }, 500);
        });
      })
      .then(console.log);
    
    console.log(4);
    

完善后的完整代码

class MyPromise {
  // Promise/A+规范的三种状态
  static PENDING = "pending";
  static FULFILLED = "fulfilled";
  static REJECTED = "rejected";

  constructor(executor) {
    this._status = MyPromise.PENDING;
    this._resolveQueue = [];
    this._rejectQueue = [];

    const _resolve = (val) => {
      const run = () => {
        if (this._status !== MyPromise.PENDING) return;
        this._value = val;
        this._status = MyPromise.FULFILLED;
        while (this._resolveQueue.length) {
          const callback = this._resolveQueue.shift();
          callback();
        }
      };
      setTimeout(run);
    };

    const _reject = (val) => {
      const run = () => {
        if (this._status !== MyPromise.PENDING) return;
        this._value = val;
        this._status = MyPromise.REJECTED;
        while (this._rejectQueue.length) {
          const callback = this._rejectQueue.shift();
          callback();
        }
      };
      setTimeout(run);
    };

    executor(_resolve, _reject);
  }

  then(resolveFn, rejectFn) {
    // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
    if (typeof resolveFn !== "function") {
      resolveFn = (value) => value;
    }
    if (typeof rejectFn !== "function") {
      rejectFn = (reason) => {
        throw new Error(reason instanceof Error ? reason.message : reason);
      };
    }

    return new MyPromise((resolve, reject) => {
      // 封装 resolveFn 回调的执行结果
      const fulfilledFn = () => {
        try {
          const res = resolveFn(this._value);
          if (res instanceof MyPromise) {
            res.then(resolve);
          } else {
            resolve(res);
          }
        } catch (error) {
          reject(error);
        }
      };

      // 封装 rejectFn 回调的执行结果
      const rejectedFn = () => {
        try {
          const res = rejectFn(this._value);
          if (res instanceof MyPromise) {
            res.then(resolve, reject);
          } else {
            resolve(res);
          }
        } catch (err) {
          reject(err);
        }
      };

      switch (this._status) {
        // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
        case MyPromise.PENDING:
          this._resolveQueue.push(fulfilledFn);
          this._rejectQueue.push(rejectedFn);
          break;
        // 当状态已经变为resolve/reject时,直接执行then回调
        case MyPromise.FULFILLED:
          fulfilledFn(this._value); // this._value是上一个then回调return的值(见完整版代码)
          break;
        case MyPromise.REJECTED:
          rejectedFn(this._value);
          break;
      }
    });
  }
}

其他方法

Promise.prototype.catch()

// catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
  return this.then(undefined, rejectFn)
}

Promise.prototype.finally()

// finally方法
finally(callback) {
  return this.then(
    // MyPromise.resolve执行回调,并在then中return结果传递给后面的Promise
    value => MyPromise.resolve(callback()).then(() => value),
    reason => MyPromise.resolve(callback()).then(() => { throw reason })  // reject同理
  )
}

Promise.resolve()

// 静态 resolve 方法
static resolve(value) {
  if(value instanceof MyPromise) return value // 根据规范, 如果参数是Promise实例, 直接return这个实例
  return new MyPromise(resolve => resolve(value))
}

Promise.reject()

// 静态 reject 方法
static reject(reason) {
  return new MyPromise((resolve, reject) => reject(reason))
}

Promise.all()

Promise.all(iterable) 方法接收一个可迭代对象作为参数,待其中的所有 Promise 都 fulfilled 之后执行其回调。

iterable 中的元素如果不是 Promise 对象则直接返回。

如果有一个 Promise 的状态变为 rejected,则 reject() 这个 Promise 的值,否则 resolve() 所有 Promise 值的数组。

class MyPromise {
  // ...
  static all(promiseArr) {
    const result = [];

    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((promiseEvent) => {
        MyPromise.resolve(promiseEvent)
          .then((value) => {
            result.push(value);
            if (result.length === promiseArr.length) {
              resolve(result);
            }
          })
          .catch(reject);
      });
    });
  }
}

Promise.race()

Promise.race(iterable) 接收的参数与 Promise.race() 方法相同,区别在于 race 方法的返回值为 iterable 中第一个状态变化的 Promise 的返回值(状态也与该 Promise 一致)。

class MyPromise {
  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((promiseEvent) => {
        MyPromise.resolve(promiseEvent).then(resolve).catch(reject);
      });
    });
  }
}

参考

9k 字 | Promise/async/Generator 实现原理解析open in new window