手写 Promise
问题
如何手写代码实现一个简单的 Promise?
答案
实现一个符合 Promises/A+ 规范的 Promise,需要理解其核心概念:状态管理、回调存储、链式调用。
核心概念
Promise 三种状态
- pending:初始状态,可以转换为 fulfilled 或 rejected
- fulfilled:操作成功完成
- rejected:操作失败
状态一旦改变就不可逆转。
基础版实现
type PromiseState = 'pending' | 'fulfilled' | 'rejected';
type Resolve<T> = (value: T) => void;
type Reject = (reason: any) => void;
type Executor<T> = (resolve: Resolve<T>, reject: Reject) => void;
class MyPromise<T> {
private state: PromiseState = 'pending';
private value: T | undefined = undefined;
private reason: any = undefined;
private onFulfilledCallbacks: Array<() => void> = [];
private onRejectedCallbacks: Array<() => void> = [];
constructor(executor: Executor<T>) {
const resolve: Resolve<T> = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject: Reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
// 执行所有失败回调
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
): MyPromise<TResult1 | TResult2> {
// 参数默认值处理
const realOnFulfilled = typeof onFulfilled === 'function'
? onFulfilled
: (value: T) => value as unknown as TResult1;
const realOnRejected = typeof onRejected === 'function'
? onRejected
: (reason: any) => { throw reason; };
// 返回新的 Promise 实现链式调用
const promise2 = new MyPromise<TResult1 | TResult2>((resolve, reject) => {
const handleFulfilled = () => {
// 使用微任务确保异步执行
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value as T);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const handleRejected = () => {
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.state === 'fulfilled') {
handleFulfilled();
} else if (this.state === 'rejected') {
handleRejected();
} else {
// pending 状态,存储回调
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
return promise2;
}
// 处理 then 返回值
private resolvePromise<R>(
promise2: MyPromise<R>,
x: any,
resolve: Resolve<R>,
reject: Reject
): void {
// 防止循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
// 如果 x 是 Promise,等待其完成
if (x instanceof MyPromise) {
x.then(resolve, reject);
return;
}
// 如果 x 是 thenable 对象
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
(y: any) => {
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
(r: any) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}
catch<TResult = never>(
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
): MyPromise<T | TResult> {
return this.then(null, onRejected);
}
finally(onFinally?: (() => void) | null): MyPromise<T> {
return this.then(
value => {
onFinally?.();
return value;
},
reason => {
onFinally?.();
throw reason;
}
);
}
}
静态方法实现
class MyPromise<T> {
// ... 上面的代码 ...
static resolve<U>(value: U | PromiseLike<U>): MyPromise<U> {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise<U>(resolve => resolve(value as U));
}
static reject<U = never>(reason: any): MyPromise<U> {
return new MyPromise<U>((_, reject) => reject(reason));
}
static all<T extends readonly unknown[]>(
promises: T
): MyPromise<{ -readonly [P in keyof T]: Awaited<T[P]> }> {
return new MyPromise((resolve, reject) => {
const results: any[] = [];
let completedCount = 0;
const promiseArray = Array.from(promises);
if (promiseArray.length === 0) {
resolve([] as any);
return;
}
promiseArray.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value;
completedCount++;
if (completedCount === promiseArray.length) {
resolve(results as any);
}
},
reason => {
reject(reason);
}
);
});
});
}
static race<T extends readonly unknown[]>(
promises: T
): MyPromise<Awaited<T[number]>> {
return new MyPromise((resolve, reject) => {
const promiseArray = Array.from(promises);
promiseArray.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
}
static allSettled<T extends readonly unknown[]>(
promises: T
): MyPromise<{ -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>> }> {
return new MyPromise(resolve => {
const results: PromiseSettledResult<any>[] = [];
let completedCount = 0;
const promiseArray = Array.from(promises);
if (promiseArray.length === 0) {
resolve([] as any);
return;
}
promiseArray.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = { status: 'fulfilled', value };
completedCount++;
if (completedCount === promiseArray.length) {
resolve(results as any);
}
},
reason => {
results[index] = { status: 'rejected', reason };
completedCount++;
if (completedCount === promiseArray.length) {
resolve(results as any);
}
}
);
});
});
}
static any<T extends readonly unknown[]>(
promises: T
): MyPromise<Awaited<T[number]>> {
return new MyPromise((resolve, reject) => {
const errors: any[] = [];
let rejectedCount = 0;
const promiseArray = Array.from(promises);
if (promiseArray.length === 0) {
reject(new AggregateError([], 'All promises were rejected'));
return;
}
promiseArray.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
resolve(value);
},
reason => {
errors[index] = reason;
rejectedCount++;
if (rejectedCount === promiseArray.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
});
}
}
使用示例
// 基本使用
const promise = new MyPromise<string>((resolve, reject) => {
setTimeout(() => {
resolve('成功');
}, 1000);
});
promise
.then(value => {
console.log(value); // '成功'
return value + '!';
})
.then(value => {
console.log(value); // '成功!'
})
.catch(error => {
console.error(error);
});
// Promise.all
MyPromise.all([
MyPromise.resolve(1),
MyPromise.resolve(2),
MyPromise.resolve(3)
]).then(results => {
console.log(results); // [1, 2, 3]
});
// Promise.race
MyPromise.race([
new MyPromise(resolve => setTimeout(() => resolve('慢'), 200)),
new MyPromise(resolve => setTimeout(() => resolve('快'), 100))
]).then(result => {
console.log(result); // '快'
});
关键点总结
| 要点 | 说明 |
|---|---|
| 状态不可逆 | pending 只能变为 fulfilled 或 rejected,且只能变化一次 |
| 异步执行 | then 的回调必须异步执行(微任务) |
| 链式调用 | then 返回新的 Promise |
| 值穿透 | then 参数不是函数时,值会穿透到下一个 then |
| 错误冒泡 | 错误会沿着链传递直到被 catch 捕获 |
注意事项
queueMicrotask用于模拟原生 Promise 的微任务行为resolvePromise方法处理了各种返回值情况,包括 thenable 对象- 需要防止循环引用(then 返回的 Promise 不能是自身)
常见面试问题
Q1: Promise 有哪几种状态?状态之间如何转换?
答案:
Promise 有三种状态:
- pending:初始状态,等待中
- fulfilled:操作成功完成
- rejected:操作失败
关键规则:
- 状态只能从 pending 变为 fulfilled 或 rejected
- 状态一旦改变就不可逆转(immutable)
- 不能从 fulfilled 变为 rejected,反之亦然
Q2: Promise 的 then 方法为什么返回新的 Promise?
答案:
返回新 Promise 是为了实现链式调用,使异步操作可以像同步代码一样顺序执行:
// 链式调用
fetch('/api/user')
.then(res => res.json()) // 返回新 Promise
.then(user => fetch(`/api/posts/${user.id}`))
.then(res => res.json())
.then(posts => console.log(posts))
.catch(err => console.error(err)); // 统一错误处理
如果 then 返回同一个 Promise,会导致:
- 无法知道链中每一步的状态
- 错误处理变得复杂
- 无法实现"值的传递"
Q3: Promise.all、Promise.race、Promise.allSettled、Promise.any 的区别?
答案:
const p1 = Promise.resolve(1);
const p2 = Promise.reject('error');
const p3 = new Promise(resolve => setTimeout(() => resolve(3), 100));
| 方法 | 行为 | 成功条件 | 失败条件 |
|---|---|---|---|
all | 全部并行执行 | 全部成功 | 任一失败 |
race | 返回最快的结果 | 最快的成功 | 最快的失败 |
allSettled | 等待全部完成 | 永远成功 | - |
any | 返回最快成功的 | 任一成功 | 全部失败 |
// Promise.all - 一个失败就失败
Promise.all([p1, p2, p3]).catch(e => console.log(e)); // 'error'
// Promise.allSettled - 返回所有结果
Promise.allSettled([p1, p2, p3]).then(results => {
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 }
// ]
});
// Promise.any - 返回第一个成功的
Promise.any([p1, p2, p3]).then(v => console.log(v)); // 1
// Promise.race - 返回最快的(无论成功失败)
Promise.race([p1, p2, p3]).then(v => console.log(v)); // 1(p1 先完成)
Q4: 如何实现 Promise 的并发控制?
答案:
async function asyncPool<T>(
limit: number,
items: T[],
iteratorFn: (item: T) => Promise<any>
): Promise<any[]> {
const results: Promise<any>[] = [];
const executing: Promise<any>[] = [];
for (const item of items) {
const p = Promise.resolve().then(() => iteratorFn(item));
results.push(p);
if (items.length >= limit) {
const e = p.then(() => {
executing.splice(executing.indexOf(e), 1);
});
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
// 使用示例:最多同时发送 3 个请求
const urls = ['/api/1', '/api/2', '/api/3', '/api/4', '/api/5'];
asyncPool(3, urls, url => fetch(url).then(r => r.json()));
Q5: Promise 和 async/await 的关系是什么?
答案:
async/await 是 Promise 的语法糖,让异步代码看起来像同步代码:
// Promise 写法
function fetchUser() {
return fetch('/api/user')
.then(res => res.json())
.then(user => {
return fetch(`/api/posts/${user.id}`);
})
.then(res => res.json());
}
// async/await 写法(等价)
async function fetchUser() {
const res1 = await fetch('/api/user');
const user = await res1.json();
const res2 = await fetch(`/api/posts/${user.id}`);
return res2.json();
}
关键点:
async函数总是返回 Promiseawait只能在async函数内使用await后面的 Promise rejected 会抛出异常,需要 try/catch
Q6: 实现 Promise.resolve 需要注意什么?
答案:
class MyPromise<T> {
static resolve<U>(value: U | PromiseLike<U>): MyPromise<U> {
// 1. 如果是 Promise 实例,直接返回
if (value instanceof MyPromise) {
return value;
}
// 2. 如果是 thenable 对象,等待其完成
if (value !== null && typeof value === 'object' && 'then' in value) {
return new MyPromise((resolve, reject) => {
(value as PromiseLike<U>).then(resolve, reject);
});
}
// 3. 普通值,包装成 fulfilled 的 Promise
return new MyPromise(resolve => resolve(value as U));
}
}
注意点:
- 传入 Promise 实例时直接返回,不要再包装
- 支持 thenable 对象(有 then 方法的对象)
- 普通值直接 resolve
Q7: 什么是微任务(Microtask)?Promise 为什么使用微任务?
答案:
JavaScript 的事件循环中,任务分为:
- 宏任务(Macrotask):setTimeout、setInterval、I/O、UI 渲染
- 微任务(Microtask):Promise.then、MutationObserver、queueMicrotask
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出顺序:1, 4, 3, 2
// 同步代码 → 微任务 → 宏任务
Promise 使用微任务的原因:
- 更快的响应:微任务在当前宏任务结束后立即执行
- 避免 UI 阻塞:在 DOM 更新前执行
- 保持执行顺序的可预测性
Q8: 如何实现 Promise 的链式调用?then 返回新 Promise 的原因?
答案:
Promise 链式调用的关键在于 then 方法返回一个新的 Promise,而不是返回 this(当前 Promise)。
为什么不能返回 this?
// 假设 then 返回 this(错误做法)
class BadPromise<T> {
private state: 'pending' | 'fulfilled' | 'rejected' = 'pending';
private value: T | undefined;
then(onFulfilled: (value: T) => any): BadPromise<T> {
// 如果返回 this,所有 then 共享同一个 Promise
// 无法表示链中每一步的独立状态
return this;
}
}
const p = new BadPromise<number>();
p.then(v => v + 1) // 返回 this,状态和值都没变
.then(v => v * 2); // 还是同一个 Promise,无法获取上一步的返回值
正确实现:返回新 Promise
class MyPromise<T> {
then<TResult>(
onFulfilled?: ((value: T) => TResult | PromiseLike<TResult>) | null
): MyPromise<TResult> {
// 返回新的 Promise
const promise2 = new MyPromise<TResult>((resolve, reject) => {
const handleFulfilled = (): void => {
queueMicrotask(() => {
try {
// 执行回调,获取返回值
const x = onFulfilled
? onFulfilled(this.value as T)
: (this.value as unknown as TResult);
// 根据返回值决定新 Promise 的状态
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.state === 'fulfilled') {
handleFulfilled();
} else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(handleFulfilled);
}
});
return promise2;
}
}
值穿透机制:
// 当 then 的参数不是函数时,值会"穿透"到下一个 then
Promise.resolve(42)
.then(null) // 不是函数,值穿透
.then(undefined) // 不是函数,值穿透
.then(v => {
console.log(v); // 42,值从第一个 resolve 一路穿透到这里
});
// 实现原理:
const realOnFulfilled = typeof onFulfilled === 'function'
? onFulfilled
: (value: T) => value as unknown as TResult; // 直接返回值,实现穿透
处理 then 回调返回 thenable 的情况:
function resolvePromise<T>(
promise2: MyPromise<T>,
x: any,
resolve: (value: T) => void,
reject: (reason: any) => void
): void {
// 1. 防止循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
// 2. 如果 x 是 Promise,等待其结果
if (x instanceof MyPromise) {
x.then(resolve, reject);
return;
}
// 3. 如果 x 是 thenable(有 then 方法的对象)
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
(y: any) => {
if (called) return;
called = true;
// 递归解析,因为 y 可能还是一个 thenable
resolvePromise(promise2, y, resolve, reject);
},
(r: any) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (error) {
if (!called) {
reject(error);
}
}
} else {
// 4. x 是普通值,直接 resolve
resolve(x);
}
}
Q9: Promise 中的微任务是如何工作的?为什么要用微任务?
答案:
Promise 的 then/catch/finally 回调总是异步执行,并且是作为微任务(Microtask) 而非宏任务(Macrotask)执行的。
微任务的执行时机:
console.log('1. 同步代码');
setTimeout(() => {
console.log('5. 宏任务 (setTimeout)');
}, 0);
Promise.resolve()
.then(() => {
console.log('3. 微任务 1');
return Promise.resolve();
})
.then(() => {
console.log('4. 微任务 2');
});
console.log('2. 同步代码');
// 输出顺序:1 → 2 → 3 → 4 → 5
// 同步代码 → 微任务队列(全部清空) → 宏任务队列
为什么要用微任务而非同步执行?
// 假设 then 回调是同步执行的(错误做法)
const p = new Promise<number>((resolve) => {
resolve(1);
});
let value = 0;
p.then(v => {
value = v;
});
console.log(value); // 如果同步:1;如果异步:0
// 问题:同步执行会导致行为不可预测
// 根据 Promise 是立即 resolve 还是延迟 resolve,代码行为不同
// 微任务保证异步一致性
const p1 = Promise.resolve(1); // 立即 resolve
const p2 = new Promise<number>(resolve => {
setTimeout(() => resolve(2), 100); // 延迟 resolve
});
// 无论哪种情况,then 回调都是异步执行的
// 代码行为一致且可预测
p1.then(v => console.log(v));
console.log('先执行'); // 总是先输出 "先执行",再输出 1
queueMicrotask vs setTimeout:
// 在手写 Promise 中使用微任务
class MyPromise<T> {
then(onFulfilled: (value: T) => void): MyPromise<any> {
return new MyPromise((resolve, reject) => {
const handler = (): void => {
// 推荐:queueMicrotask(真正的微任务)
queueMicrotask(() => {
const result = onFulfilled(this.value as T);
resolve(result);
});
// 不推荐:setTimeout(宏任务,时机更晚)
// setTimeout(() => {
// const result = onFulfilled(this.value as T);
// resolve(result);
// }, 0);
};
if (this.state === 'fulfilled') {
handler();
} else {
this.onFulfilledCallbacks.push(handler);
}
});
}
}
| 方式 | 类型 | 执行时机 | 用途 |
|---|---|---|---|
queueMicrotask | 微任务 | 当前宏任务末尾 | 推荐,符合原生 Promise 行为 |
Promise.resolve().then | 微任务 | 当前宏任务末尾 | 可替代 queueMicrotask |
MutationObserver | 微任务 | 当前宏任务末尾 | Vue 2 的 nextTick 曾使用 |
setTimeout(fn, 0) | 宏任务 | 下一次事件循环 | 不推荐,执行时机太晚 |
Q10: 手写一个符合 Promises/A+ 规范的 Promise 需要注意哪些边界情况?
答案:
Promises/A+ 规范有严格的要求,以下是最容易遗漏的边界情况:
1. 循环引用检测:
// then 返回的 Promise 不能是自身
const p = Promise.resolve(1);
const p2 = p.then(() => p2); // TypeError: Chaining cycle detected
// 实现:
function resolvePromise<T>(
promise2: MyPromise<T>,
x: any,
resolve: (value: T) => void,
reject: (reason: any) => void
): void {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// ...
}
2. 多次 resolve/reject 只有第一次生效:
// 规范要求:resolve 或 reject 只能调用一次
const p = new Promise<number>((resolve, reject) => {
resolve(1);
resolve(2); // 忽略
reject('err'); // 忽略
});
// p 的值是 1
// 实现:
const resolve = (value: T): void => {
if (this.state !== 'pending') return; // 状态已变,忽略
this.state = 'fulfilled';
this.value = value;
};
3. thenable 对象处理:
// 不只是 Promise 实例,任何有 then 方法的对象都需要处理
const thenable = {
then(resolve: (v: number) => void) {
resolve(42);
}
};
Promise.resolve(thenable).then(v => {
console.log(v); // 42,不是 thenable 对象本身
});
// 甚至要处理 then 属性访问时抛错的情况
const badThenable = Object.defineProperty({}, 'then', {
get() {
throw new Error('访问 then 属性时出错');
}
});
// 实现中必须用 try/catch 包裹 then 属性的访问
try {
const then = x.then; // 可能抛错!
if (typeof then === 'function') {
// ...
}
} catch (error) {
reject(error);
}
4. thenable 的 resolve/reject 也只能调用一次:
// 恶意的 thenable 可能多次调用 resolve 和 reject
const trickyThenable = {
then(resolve: (v: number) => void, reject: (r: any) => void) {
resolve(1);
resolve(2); // 应被忽略
reject('err'); // 应被忽略
}
};
// 实现:使用 called 标志
let called = false;
then.call(
x,
(y: any) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
(r: any) => {
if (called) return;
called = true;
reject(r);
}
);
5. then 的参数不是函数时的值穿透:
// 规范要求:如果 onFulfilled 不是函数,必须被忽略(值穿透)
Promise.resolve('hello')
.then(123 as any) // 不是函数,忽略
.then(null) // 不是函数,忽略
.then(v => console.log(v)); // 'hello'
// 同样,onRejected 不是函数时,错误要穿透
Promise.reject('error')
.then(v => v) // 没有 onRejected,错误穿透
.catch(e => console.log(e)); // 'error'
// 实现:
const realOnFulfilled = typeof onFulfilled === 'function'
? onFulfilled
: (value: T) => value as unknown as TResult; // 值穿透
const realOnRejected = typeof onRejected === 'function'
? onRejected
: (reason: any) => { throw reason; }; // 错误穿透
6. 异步 resolve 的处理:
// executor 中的 resolve 可能在未来某个时间调用
const p = new MyPromise<string>((resolve) => {
// 异步 resolve
setTimeout(() => resolve('延迟值'), 1000);
});
p.then(v => console.log(v)); // 1秒后输出 "延迟值"
// 实现:当 state 还是 pending 时,将回调存入队列
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
完整边界情况清单:
| 边界情况 | 处理方式 |
|---|---|
| 循环引用 | promise2 === x 时 reject TypeError |
| 多次 resolve/reject | 检查 state 是否为 pending |
| thenable 对象 | 检查 then 属性是否为函数 |
| then 属性访问出错 | try/catch 包裹 |
| thenable 多次回调 | called 标志位 |
| 参数非函数 | 值穿透 / 错误穿透 |
| 异步 resolve | 回调队列 |
| executor 抛错 | try/catch 转为 reject |