跳到主要内容

手写 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:操作失败

关键规则

  1. 状态只能从 pending 变为 fulfilled 或 rejected
  2. 状态一旦改变就不可逆转(immutable)
  3. 不能从 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,会导致:

  1. 无法知道链中每一步的状态
  2. 错误处理变得复杂
  3. 无法实现"值的传递"

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();
}

关键点

  1. async 函数总是返回 Promise
  2. await 只能在 async 函数内使用
  3. 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));
}
}

注意点

  1. 传入 Promise 实例时直接返回,不要再包装
  2. 支持 thenable 对象(有 then 方法的对象)
  3. 普通值直接 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 使用微任务的原因

  1. 更快的响应:微任务在当前宏任务结束后立即执行
  2. 避免 UI 阻塞:在 DOM 更新前执行
  3. 保持执行顺序的可预测性

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

相关链接