Skip to content

深入理解 Promise(手写完整实现)

从零实现符合 Promises/A+ 规范的 Promise,理解每一行代码背后的设计思想

什么是 Promise?

定义:Promise 是 ES6 引入的异步编程解决方案,它代表一个异步操作的最终结果。一个 Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败),状态一旦改变就不可逆转。

涉及场景

  • 网络请求fetch API 返回 Promise,是最常见的使用场景
  • 文件操作:Node.js 的 fs.promises 提供 Promise 化的文件 API
  • 定时器封装:将 setTimeout 包装为 Promise 实现可 await 的延时
  • 并发控制Promise.all / Promise.race / Promise.allSettled 管理多个异步任务
  • 数据库操作:几乎所有现代 ORM/数据库驱动都基于 Promise
  • 动画/过渡:Web Animations API 的 .finished 属性返回 Promise

作用

  1. 解决回调地狱:链式 .then() 替代层层嵌套的回调函数
  2. 统一错误处理.catch() 集中捕获整条链上的错误
  3. async/await 基石async 函数本质上是 Promise 的语法糖
  4. 可组合性:多个 Promise 可通过 allraceany 等组合为新的 Promise

Promise 的诞生背景

回调地狱(Callback Hell)

javascript
// 2015年之前,异步编程只能用回调
login(user, password, (err, token) => {
  if (err) return handleError(err);
  getProfile(token, (err, profile) => {
    if (err) return handleError(err);
    getOrders(profile.id, (err, orders) => {
      if (err) return handleError(err);
      getOrderDetail(orders[0].id, (err, detail) => {
        if (err) return handleError(err);
        // 嵌套地狱...
      });
    });
  });
});

问题:

  • 代码不断右移(金字塔式嵌套)
  • 错误处理散落各处
  • 无法方便地组合多个异步操作
  • 控制反转——将回调交给第三方库,无法保证调用次数和时机

Promise 解决了什么

javascript
// Promise 链式调用
login(user, password)
  .then(token => getProfile(token))
  .then(profile => getOrders(profile.id))
  .then(orders => getOrderDetail(orders[0].id))
  .then(detail => render(detail))
  .catch(err => handleError(err)); // 统一错误处理

Promises/A+ 规范核心

Promise 必须处于以下三种状态之一:

                 resolve(value)
   ┌──────────┐ ──────────────→ ┌───────────┐
   │  pending  │                 │ fulfilled  │
   │  (等待中) │                 │  (已完成)   │
   └──────────┘ ──────────────→ └───────────┘
        │        reject(reason)
        │       ──────────────→ ┌───────────┐
        └──────────────────────→│ rejected   │
                                │  (已拒绝)   │
                                └───────────┘

规则:
1. pending 可以转变为 fulfilled 或 rejected
2. fulfilled/rejected 后状态不可再变(不可逆)
3. fulfilled 必须有一个不可变的 value
4. rejected 必须有一个不可变的 reason

手写完整 Promise

第一步:基础结构

javascript
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  #state = PENDING;
  #result = undefined;
  #handlers = []; // 存储 .then() 注册的回调

  constructor(executor) {
    const resolve = (value) => {
      if (this.#state !== PENDING) return; // 状态锁定
      this.#state = FULFILLED;
      this.#result = value;
      this.#handlers.forEach(h => h.onFulfilled(value));
    };

    const reject = (reason) => {
      if (this.#state !== PENDING) return;
      this.#state = REJECTED;
      this.#result = reason;
      this.#handlers.forEach(h => h.onRejected(reason));
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err); // executor 中抛出的错误自动 reject
    }
  }
}

第二步:实现 then 方法(核心)

then 方法是 Promise 的核心,规范要求:

  1. 接受两个可选参数:onFulfilledonRejected
  2. 必须返回一个新的 Promise(链式调用的基础)
  3. 回调必须异步执行(微任务)
javascript
class MyPromise {
  #state = PENDING;
  #result = undefined;
  #handlers = [];

  constructor(executor) {
    const resolve = (value) => {
      // 如果 resolve 的是一个 Promise/thenable,需要递归解析
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
        return;
      }
      if (this.#state !== PENDING) return;
      this.#state = FULFILLED;
      this.#result = value;
      // 状态变化后,执行所有等待的回调
      this.#runHandlers();
    };

    const reject = (reason) => {
      if (this.#state !== PENDING) return;
      this.#state = REJECTED;
      this.#result = reason;
      this.#runHandlers();
    };

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

  #runHandlers() {
    // 异步执行所有已注册的回调
    this.#handlers.forEach(handler => this.#runHandler(handler));
    this.#handlers = []; // 清空
  }

  #runHandler({ onFulfilled, onRejected }) {
    // 用 queueMicrotask 模拟微任务调度
    queueMicrotask(() => {
      if (this.#state === FULFILLED) {
        onFulfilled(this.#result);
      } else if (this.#state === REJECTED) {
        onRejected(this.#result);
      }
    });
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const handler = {
        onFulfilled: (value) => {
          // 如果 onFulfilled 不是函数,值穿透
          if (typeof onFulfilled !== 'function') {
            resolve(value);
            return;
          }
          try {
            const result = onFulfilled(value);
            // 如果回调返回一个 Promise,等它完成
            if (result instanceof MyPromise) {
              result.then(resolve, reject);
            } else {
              resolve(result);
            }
          } catch (err) {
            reject(err);
          }
        },
        onRejected: (reason) => {
          if (typeof onRejected !== 'function') {
            reject(reason); // 错误穿透
            return;
          }
          try {
            const result = onRejected(reason);
            if (result instanceof MyPromise) {
              result.then(resolve, reject);
            } else {
              resolve(result); // ⚠️ 注意:是 resolve,不是 reject
            }
          } catch (err) {
            reject(err);
          }
        }
      };

      if (this.#state === PENDING) {
        // Promise 还在等待中,存储回调
        this.#handlers.push(handler);
      } else {
        // Promise 已经完成/拒绝,直接执行(异步)
        this.#runHandler(handler);
      }
    });
  }
}

第三步:实现 catch 和 finally

javascript
class MyPromise {
  // ... 上面的代码 ...

  catch(onRejected) {
    // catch 就是 then 的语法糖
    return this.then(undefined, onRejected);
  }

  finally(onFinally) {
    // finally 的特殊行为:
    // 1. 不接收任何参数
    // 2. 值穿透(不影响链式调用的结果)
    // 3. 如果 onFinally 返回 rejected Promise,会替换原结果
    return this.then(
      (value) => {
        return MyPromise.resolve(onFinally()).then(() => value);
      },
      (reason) => {
        return MyPromise.resolve(onFinally()).then(() => { throw reason; });
      }
    );
  }
}

第四步:静态方法

javascript
class MyPromise {
  // ... 上面的代码 ...

  static resolve(value) {
    // 如果已经是 Promise,直接返回
    if (value instanceof MyPromise) return value;
    return new MyPromise(resolve => resolve(value));
  }

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

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const results = [];
      let count = 0;
      const items = Array.from(promises);

      if (items.length === 0) {
        resolve(results);
        return;
      }

      items.forEach((item, index) => {
        MyPromise.resolve(item).then(
          (value) => {
            results[index] = value; // 保持顺序
            count++;
            if (count === items.length) {
              resolve(results);
            }
          },
          reject // 任何一个失败,整体 reject
        );
      });
    });
  }

  static allSettled(promises) {
    return new MyPromise((resolve) => {
      const results = [];
      let count = 0;
      const items = Array.from(promises);

      if (items.length === 0) {
        resolve(results);
        return;
      }

      items.forEach((item, index) => {
        MyPromise.resolve(item).then(
          (value) => {
            results[index] = { status: 'fulfilled', value };
            count++;
            if (count === items.length) resolve(results);
          },
          (reason) => {
            results[index] = { status: 'rejected', reason };
            count++;
            if (count === items.length) resolve(results);
          }
        );
      });
    });
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      const items = Array.from(promises);
      items.forEach(item => {
        MyPromise.resolve(item).then(resolve, reject);
      });
      // 第一个完成(无论成功失败)的结果作为整体结果
    });
  }

  static any(promises) {
    return new MyPromise((resolve, reject) => {
      const errors = [];
      let count = 0;
      const items = Array.from(promises);

      if (items.length === 0) {
        reject(new AggregateError([], 'All promises were rejected'));
        return;
      }

      items.forEach((item, index) => {
        MyPromise.resolve(item).then(
          resolve, // 任何一个成功,整体 resolve
          (reason) => {
            errors[index] = reason;
            count++;
            if (count === items.length) {
              reject(new AggregateError(errors, 'All promises were rejected'));
            }
          }
        );
      });
    });
  }

  // ES2024 新增
  static withResolvers() {
    let resolve, reject;
    const promise = new MyPromise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve, reject };
  }
}

完整代码(可运行)

javascript
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  #state = PENDING;
  #result = undefined;
  #handlers = [];

  constructor(executor) {
    const resolve = (value) => {
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
        return;
      }
      if (this.#state !== PENDING) return;
      this.#state = FULFILLED;
      this.#result = value;
      this.#runHandlers();
    };

    const reject = (reason) => {
      if (this.#state !== PENDING) return;
      this.#state = REJECTED;
      this.#result = reason;
      this.#runHandlers();
    };

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

  #runHandlers() {
    this.#handlers.forEach(handler => this.#runHandler(handler));
    this.#handlers = [];
  }

  #runHandler({ onFulfilled, onRejected }) {
    queueMicrotask(() => {
      if (this.#state === FULFILLED) {
        onFulfilled(this.#result);
      } else if (this.#state === REJECTED) {
        onRejected(this.#result);
      }
    });
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const handler = {
        onFulfilled: (value) => {
          if (typeof onFulfilled !== 'function') {
            resolve(value);
            return;
          }
          try {
            const result = onFulfilled(value);
            if (result instanceof MyPromise) {
              result.then(resolve, reject);
            } else {
              resolve(result);
            }
          } catch (err) {
            reject(err);
          }
        },
        onRejected: (reason) => {
          if (typeof onRejected !== 'function') {
            reject(reason);
            return;
          }
          try {
            const result = onRejected(reason);
            if (result instanceof MyPromise) {
              result.then(resolve, reject);
            } else {
              resolve(result);
            }
          } catch (err) {
            reject(err);
          }
        }
      };

      if (this.#state === PENDING) {
        this.#handlers.push(handler);
      } else {
        this.#runHandler(handler);
      }
    });
  }

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

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

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

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

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const results = [];
      let count = 0;
      const items = Array.from(promises);
      if (items.length === 0) return resolve(results);
      items.forEach((item, index) => {
        MyPromise.resolve(item).then(
          (value) => {
            results[index] = value;
            if (++count === items.length) resolve(results);
          },
          reject
        );
      });
    });
  }

  static allSettled(promises) {
    return new MyPromise((resolve) => {
      const results = [];
      let count = 0;
      const items = Array.from(promises);
      if (items.length === 0) return resolve(results);
      items.forEach((item, index) => {
        MyPromise.resolve(item).then(
          (value) => {
            results[index] = { status: 'fulfilled', value };
            if (++count === items.length) resolve(results);
          },
          (reason) => {
            results[index] = { status: 'rejected', reason };
            if (++count === items.length) resolve(results);
          }
        );
      });
    });
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      Array.from(promises).forEach(item => {
        MyPromise.resolve(item).then(resolve, reject);
      });
    });
  }

  static any(promises) {
    return new MyPromise((resolve, reject) => {
      const errors = [];
      let count = 0;
      const items = Array.from(promises);
      if (items.length === 0) {
        return reject(new AggregateError([], 'All promises were rejected'));
      }
      items.forEach((item, index) => {
        MyPromise.resolve(item).then(resolve, (reason) => {
          errors[index] = reason;
          if (++count === items.length) {
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        });
      });
    });
  }

  static withResolvers() {
    let resolve, reject;
    const promise = new MyPromise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve, reject };
  }
}

测试验证

javascript
// 测试1:基本功能
const p1 = new MyPromise((resolve) => {
  setTimeout(() => resolve('hello'), 100);
});
p1.then(v => console.log(v)); // 'hello'

// 测试2:链式调用
MyPromise.resolve(1)
  .then(v => v + 1)
  .then(v => v * 2)
  .then(v => console.log(v)); // 4

// 测试3:错误处理
MyPromise.reject('error')
  .then(v => console.log('不会执行'))
  .catch(e => {
    console.log('caught:', e); // 'caught: error'
    return 'recovered';
  })
  .then(v => console.log(v)); // 'recovered'

// 测试4:finally
MyPromise.resolve('value')
  .finally(() => console.log('cleanup'))
  .then(v => console.log(v)); // 'cleanup' → 'value'(值穿透)

// 测试5:Promise.all
MyPromise.all([
  MyPromise.resolve(1),
  new MyPromise(r => setTimeout(() => r(2), 100)),
  3 // 非Promise值自动包装
]).then(v => console.log(v)); // [1, 2, 3]

// 测试6:Promise.allSettled
MyPromise.allSettled([
  MyPromise.resolve(1),
  MyPromise.reject('err'),
]).then(v => console.log(v));
// [{ status: 'fulfilled', value: 1 }, { status: 'rejected', reason: 'err' }]

// 测试7:Promise.any
MyPromise.any([
  MyPromise.reject('a'),
  MyPromise.resolve('b'),
  MyPromise.resolve('c'),
]).then(v => console.log(v)); // 'b'(第一个成功的)

// 测试8:Promise.withResolvers
const { promise, resolve } = MyPromise.withResolvers();
setTimeout(() => resolve('done'), 100);
promise.then(v => console.log(v)); // 'done'

深入理解:Resolution Procedure

Promises/A+ 规范中最复杂的部分是 Promise Resolution Procedure(2.3节),它定义了当 resolve(x) 被调用时如何处理 x

javascript
// 完整的 resolvePromise 过程(严格按规范)
function resolvePromise(promise2, x, resolve, reject) {
  // 2.3.1 如果 promise2 和 x 是同一个对象,抛出 TypeError(防止循环引用)
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }

  // 2.3.2 如果 x 是 Promise 实例,采纳它的状态
  if (x instanceof MyPromise) {
    x.then(
      (value) => resolvePromise(promise2, value, resolve, reject),
      reject
    );
    return;
  }

  // 2.3.3 如果 x 是对象或函数(可能是 thenable)
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    let called = false; // 保证只调用一次

    try {
      const then = x.then; // 2.3.3.1 取 then 属性(可能抛错)

      if (typeof then === 'function') {
        // 2.3.3.3 如果 then 是函数,以 x 为 this 调用
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject); // 递归解析
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        // 2.3.3.4 then 不是函数,直接 resolve
        resolve(x);
      }
    } catch (err) {
      // 2.3.3.2 取 then 时抛错
      if (!called) {
        reject(err);
      }
    }
  } else {
    // 2.3.4 x 不是对象或函数,直接 resolve
    resolve(x);
  }
}

为什么需要 called 标志?

javascript
// 防御恶意的 thenable
const evilThenable = {
  then(resolve, reject) {
    resolve(1);
    resolve(2); // 第二次调用应该被忽略
    reject('error'); // 这个也应该被忽略
  }
};

Promise 全部 API 速查

方法说明输入短路条件
Promise.all()全部成功才成功可迭代对象任一 reject
Promise.allSettled()等待全部完成可迭代对象无(永不短路)
Promise.race()取最快的可迭代对象任一 settle
Promise.any()取最快成功的可迭代对象任一 fulfill
Promise.resolve()包装为成功 Promise任意值-
Promise.reject()包装为失败 Promise任意值-
Promise.withResolvers()外部控制 Promise-
.then()注册成功/失败回调两个函数-
.catch()注册失败回调一个函数-
.finally()注册完成回调一个函数-

常见面试追问

1. then 中返回 Promise 和普通值的区别?

javascript
// 返回普通值:直接作为下一个 then 的参数
Promise.resolve(1).then(v => v + 1).then(v => console.log(v)); // 2

// 返回 Promise:等待这个 Promise 完成,用其结果传递
Promise.resolve(1)
  .then(v => new Promise(r => setTimeout(() => r(v + 1), 1000)))
  .then(v => console.log(v)); // 1秒后输出 2

2. catch 能捕获 then 中的错误吗?

javascript
// ✅ 能捕获前面所有 then 中的错误
Promise.resolve()
  .then(() => { throw new Error('then error'); })
  .then(() => console.log('不会执行'))
  .catch(e => console.log(e.message)); // 'then error'

// ⚠️ catch 后面还可以继续 then
Promise.reject('error')
  .catch(e => 'recovered')
  .then(v => console.log(v)); // 'recovered'

3. Promise 构造函数中的错误 vs then 中的错误

javascript
// 构造函数中的同步错误会自动 reject
new Promise(() => {
  throw new Error('sync error');
}).catch(e => console.log(e.message)); // 'sync error'

// 构造函数中的异步错误不会被捕获!
new Promise(() => {
  setTimeout(() => {
    throw new Error('async error'); // ❌ 无法被 catch 捕获
  }, 0);
}).catch(e => console.log('不会执行'));

4. 如何实现 Promise 超时?

javascript
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms);
  });
  return Promise.race([promise, timeout]);
}

// 使用
const result = await withTimeout(fetch('/api/data'), 5000);

5. 如何实现可取消的 Promise?

javascript
function makeCancellable(promise) {
  let cancelled = false;

  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then(
      value => cancelled ? reject({ cancelled: true }) : resolve(value),
      error => cancelled ? reject({ cancelled: true }) : reject(error)
    );
  });

  return {
    promise: wrappedPromise,
    cancel() { cancelled = true; }
  };
}

// 或使用 AbortController(推荐)
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
  .then(r => r.json())
  .catch(e => {
    if (e.name === 'AbortError') console.log('请求被取消');
  });

controller.abort(); // 取消请求

总结

Promise 核心设计思想:
┌─────────────────────────────────────────────────────┐
│ 1. 状态机:pending → fulfilled / rejected(不可逆)    │
│ 2. then 返回新 Promise(链式调用的基础)               │
│ 3. 回调异步执行(微任务队列)                          │
│ 4. 值穿透:非函数参数自动传递                          │
│ 5. 错误冒泡:未处理的 reject 向后传递直到被 catch      │
│ 6. Resolution Procedure:递归解析 thenable            │
└─────────────────────────────────────────────────────┘

面试重点:
• 手写基本 Promise(constructor + then + catch)
• 手写 Promise.all / race / allSettled / any
• 理解微任务调度时机
• 理解值穿透和错误冒泡
• Promise 与 async/await 的转换关系