返回

Promise 源码剖析:同步执行 resolve 的缺陷与改进

前端

Promise 源码:同步执行 resolve

在上一篇《Promise 源码:实现一个简单的 Promise》当中,我们实现了一个可以简单可用的 Promise。但它实际上还是有不少的缺陷的,比如:

  • Promise 构造函数里直接同步 resolve,则执行不到 then。
  • 只有 resolve,没有 reject。

一些开发者提出了一种解决方案,称作 microtask 队列。

microtask 队列

microtask 队列是 JavaScript 引擎维护的一个队列,里面存储了待执行的微任务(microtask)。当 JavaScript 引擎主线程执行完毕后,会在执行宏任务(macrotask)之前执行该队列中的所有微任务。

一些浏览器(如 Chrome)将微任务和宏任务都存储在同一个队列中,按顺序执行;另一些浏览器(如 Firefox)将它们存储在不同的队列中,优先执行微任务队列。

缺陷的改进

借助 microtask 队列,我们可以改进之前的 Promise 缺陷:

class Promise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

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

      this.state = 'fulfilled';
      this.value = value;

      // 将 then 回调函数放入 microtask 队列中
      queueMicrotask(() => {
        this.onFulfilledCallbacks.forEach((callback) => {
          callback(value);
        });
      });
    };

    const reject = (reason) => {
      if (this.state !== 'pending') return;

      this.state = 'rejected';
      this.reason = reason;

      // 将 then 回调函数放入 microtask 队列中
      queueMicrotask(() => {
        this.onRejectedCallbacks.forEach((callback) => {
          callback(reason);
        });
      });
    };

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

  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      const fulfilled = (value) => {
        try {
          const result = typeof onFulfilled === 'function' ? onFulfilled(value) : value;
          resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      const rejected = (reason) => {
        try {
          const result = typeof onRejected === 'function' ? onRejected(reason) : reason;
          resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(fulfilled);
        this.onRejectedCallbacks.push(rejected);
      } else if (this.state === 'fulfilled') {
        queueMicrotask(() => fulfilled(this.value));
      } else if (this.state === 'rejected') {
        queueMicrotask(() => rejected(this.reason));
      }
    });
  }
}

在改进后的 Promise 中,无论 Promise 构造函数里 resolve 还是 reject,都会将 then 回调函数放入 microtask 队列中,从而保证 then 回调函数在微任务队列中执行,而不会因为同步执行 resolve 而执行不到。