Promise 实现

1. 核心状态维护

Promise 有三种状态:PENDINGFULFILLEDREJECTED。 必须确保状态转换不可逆。

2. 完整实现 (面试版)

这是一个符合 Promise/A+ 规范的迷你版本,足以应对大部分手写题。

/**
 * 实现一个Promise
 * Promise 有三种状态:pending、fulfilled、rejected
 * 1. 状态只能从 pending 变为 fulfilled 或者 rejected,一经改变,状态就不能再变化
 * 2. 执行构造函数时,executor 立即执行
 * 3. 执行 executor 出错,立马将状态改为 rejected
 */
class MyPromise {
  // 创建一个构造函数,构造函数执行的时候 executor 立即执行
  constructor(executor) {
    this._status = MyPromise.PENDING;
    this._value = undefined;
    // 保存函数回调的队列
    this._handlers = [];

    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      // 任务出错了,直接将状态改为 rejected
      this._reject(error);
    }
  }

  /**
   * 1. 表示当前任务完成
   * 2. 改变 promise 状态为 fulfilled
   * 3. 记录成功的数据
   * @param {*} data
   */
  _resolve(data) {
    if (this._status !== MyPromise.PENDING) return;
    this._status = MyPromise.FULFILLED;
    this._value = data;

    // 状态变化时,执行处理队列中的函数
    this._runHandlers();
  }

  /**
   * 1. 表示当前任务失败
   * 2. 改变 promise 状态为 rejected
   * 3. 记录失败的原因
   * @param {*} reason
   * @returns
   */
  _reject(reason) {
    if (this._status !== MyPromise.PENDING) return;
    this._status = MyPromise.REJECTED;
    this._value = reason;

    // 状态变化时,执行处理队列中的函数
    this._runHandlers();
  }

  /**
   * 返回一个 Promise
   * @param {*} onFulfilled
   * @param {*} onRejected
   * @returns
   */
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this._handlers.push({ executor: onFulfilled, state: MyPromise.FULFILLED, resolve, reject });
      this._handlers.push({ executor: onRejected, state: MyPromise.REJECTED, resolve, reject });

      // 如果状态已决,则直接执行
      this._runHandlers();
    });
  }

  /**
   * 执行处理队列中的函数
   */
  _runHandlers() {
    // 如果状态还是 pending 挂起状态,不执行
    if (this._status === MyPromise.PENDING) return;

    // 每处理一个函数,都需要将其从队列中移除
    while (this._handlers[0]) {
      this._runHandler(this._handlers[0]);
      this._handlers.shift();
    }
  }

  _runHandler({ executor, state, resolve, reject }) {
    runMicroTask(() => {
      // 如果状态不一致,则不处理;
      // onFulfilled 处理状态为 fulfilled 的函数
      // onRejected 处理状态为 rejected 的函数
      if (state !== this._status) return;

      // 如果 onFulfilled 和 onRejected 不是函数,则需要将状态透传
      if (typeof executor !== "function") {
        this._status === MyPromise.FULFILLED ? resolve(this._value) : reject(this._value);
        return;
      }

      try {
        const result = executor(this._value);
        // 如果返回值是 Promise,则等待其状态变化
        if (isPromise(result)) {
          result.then(resolve, reject);
        } else {
          // 如果返回值不是 Promise,则直接返回
          resolve(result);
        }
      } catch (error) {
        reject(error);
      }
    });
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  finally(onFinally) {
    return this.then(
      (data) => {
        onFinally();
        return data;
      },
      (reason) => {
        onFinally();
        throw reason;
      },
    );
  }
}

MyPromise.PENDING = "pending";
MyPromise.FULFILLED = "fulfilled";
MyPromise.REJECTED = "rejected";

function runMicroTask(fn) {
  process.nextTick(fn);
}

function isPromise(obj) {
  return !!(obj && typeof obj === "object" && typeof obj.then === "function");
}

3. 静态方法实现

Promise.resolve

static resolve(data) {
    if (data instanceof MyPromise) return data;

    return new MyPromise((resolve, reject) => {
      if (isPromise(data)) {
        data.then(resolve, reject);
      } else {
        resolve(data);
      }
    });
  }

Promise.reject

static reject(reason) {
    return new MyPromise((_, reject) => {
      reject(reason);
    });
  }

Promise.all

Promise.all 用于并行执行一组相互依赖的异步任务。只有所有任务都成功时, 返回的 Promise 才会变为 fulfilled,其结果是一个包含所有成功值的数组。

需要注意以下几点:

  1. 结果数组的顺序与参数的传入顺序一致,与任务的完成顺序无关。
  2. 参数中的普通值会被当作已成功的 Promise 处理。
  3. 只要有一个任务失败,返回的 Promise 就会立即变为 rejected,失败原因是该任务的失败原因。
  4. 其他任务不会因为其中一个任务失败而自动取消,它们仍会继续执行,只是其结果不会再影响 Promise.all
  5. 传入空的可迭代对象时,会立即返回一个状态为 fulfilled、结果为 [] 的 Promise。

它适用于“所有任务必须全部成功,后续流程才能继续”的场景,例如同时请求页面所需的多组基础数据。

/**
   * 返回一个新的 Promise
   * 所有 promise 成功后才返回成功,只要有一个失败就返回失败
   * 返回结果的顺序需要保持一致
   * @param {*} promises
   */
  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const result = [];
      let count = 0;
      let fulfilledCount = 0;

      for (let pro of promises) {
        // 记录当前的索引, 用于保证结果的顺序
        let i = count;
        count++;
        // 用 resolve 包装一下,保证每个 promise 都是 promise 实例
        MyPromise.resolve(pro).then((data) => {
          result[i] = data;
          fulfilledCount++;
          if (fulfilledCount === count) {
            resolve(result);
          }
        }, reject);
      }

      if (count === 0) {
        resolve(result);
      }
    });
  }

Promise.allSettled

Promise.allSettled 同样会并行执行一组异步任务,但它不会因为某个任务失败而提前结束, 而是等待所有任务都完成后,一次性返回每个任务的执行结果。

返回数组中的每一项有两种结构:

  • 成功:{ status: 'fulfilled', value: 成功值 }
  • 失败:{ status: 'rejected', reason: 失败原因 }

它有以下特点:

  1. 返回结果的顺序与参数的传入顺序一致。
  2. 单个任务失败不会使 Promise.allSettled 返回失败。
  3. 参数中的普通值会被记录为 fulfilled 状态。
  4. 传入空的可迭代对象时,会立即返回一个状态为 fulfilled、结果为 [] 的 Promise。

它适用于任务彼此独立、需要收集全部执行结果的场景,例如批量上传文件后展示每个文件的成功或失败状态。

/**
   * 返回一个新的 Promise
   * 等待所有的 promise 都 settled 后返回结果
   * @param {*} promises
   */
  static allSettled(promises) {
    return new MyPromise((resolve, reject) => {
      const result = [];
      let count = 0;
      let fulfilledCount = 0;

      for (let pro of promises) {
        let i = count;
        count++;
        MyPromise.resolve(pro).then(
          (data) => {
            result[i] = { status: MyPromise.FULFILLED, value: data };
            fulfilledCount++;
            if (fulfilledCount === count) {
              resolve(result);
            }
          },
          (reason) => {
            result[i] = { status: MyPromise.REJECTED, reason };
            fulfilledCount++;
            if (fulfilledCount === count) {
              resolve(result);
            }
          },
        );
      }

      if (count === 0) {
        resolve(result);
      }
    });
  }

Promise.allPromise.allSettled 的核心区别是失败时是否提前结束:前者强调“全部成功”, 适合强依赖任务;后者强调“全部完成”,适合需要汇总结果的独立任务。

Promise.race

Promise.race 返回一个新的 Promise。迭代对象中第一个状态确定的 Promise, 无论成功还是失败,都会决定返回 Promise 的最终状态。

static race(promises) {
  return new MyPromise((resolve, reject) => {
    for (const promise of promises) {
      // 使用 resolve 包装,以兼容普通值和其他 thenable 对象
      MyPromise.resolve(promise).then(resolve, reject);
    }
  });
}

由于 Promise 的状态一旦确定就不能改变,因此只有最先完成的任务能够调用成功。 当传入空的可迭代对象时,循环不会执行,返回的 Promise 会一直保持 pending 状态。

const slow = new MyPromise((resolve) => {
  setTimeout(() => resolve('slow'), 100);
});

const fast = new MyPromise((resolve) => {
  setTimeout(() => resolve('fast'), 20);
});

MyPromise.race([slow, fast]).then(console.log); // fast

const failed = new MyPromise((_, reject) => {
  setTimeout(() => reject(new Error('请求失败')), 10);
});

MyPromise.race([slow, failed]).catch((error) => {
  console.log(error.message); // 请求失败
});

4. 关键考点

  1. Pending 状态不可逆
  2. 异步执行:必须使用 queueMicrotasksetTimeout 来保证 then 回调在当前宏任务之后执行。
  3. 链式调用:返回一个新的 Promise。
  4. 值穿透:如果没有传 onFulfilled,需要透传值 (x => x)。