深入理解 Promise(手写完整实现)
从零实现符合 Promises/A+ 规范的 Promise,理解每一行代码背后的设计思想
什么是 Promise?
定义:Promise 是 ES6 引入的异步编程解决方案,它代表一个异步操作的最终结果。一个 Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败),状态一旦改变就不可逆转。
涉及场景:
- 网络请求:
fetchAPI 返回 Promise,是最常见的使用场景 - 文件操作:Node.js 的
fs.promises提供 Promise 化的文件 API - 定时器封装:将
setTimeout包装为 Promise 实现可await的延时 - 并发控制:
Promise.all/Promise.race/Promise.allSettled管理多个异步任务 - 数据库操作:几乎所有现代 ORM/数据库驱动都基于 Promise
- 动画/过渡:Web Animations API 的
.finished属性返回 Promise
作用:
- 解决回调地狱:链式
.then()替代层层嵌套的回调函数 - 统一错误处理:
.catch()集中捕获整条链上的错误 - async/await 基石:
async函数本质上是 Promise 的语法糖 - 可组合性:多个 Promise 可通过
all、race、any等组合为新的 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 的核心,规范要求:
- 接受两个可选参数:
onFulfilled和onRejected - 必须返回一个新的 Promise(链式调用的基础)
- 回调必须异步执行(微任务)
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秒后输出 22. 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 的转换关系