手写 Promise(含 then/catch/finally + all/race/allSettled/any)
这是前端面试中频率最高的手写题之一。一个完整的 Promise 实现需要理解状态机、发布订阅、微任务调度以及链式调用穿透。本文从最简版本一步步演进到贴近 Promise/A+ 的实现,并补齐常用静态方法。
题目描述
请实现一个 MyPromise,满足以下要求:
- 拥有
pending / fulfilled / rejected三种状态,状态只能从pending转换一次。 - 实例方法:
then / catch / finally,支持链式调用,支持值穿透。 - 静态方法:
resolve / reject / all / race / allSettled / any。 then的回调需在微任务中执行。
思路分析
整体可以拆成三块来理解:
- 状态机:用一个字段
state持有当前状态,value/reason持有终值。resolve/reject只允许在pending时变更一次。 - 发布订阅:在
pending阶段调用then注册的回调不能立即执行,要存进两个队列(onFulfilledCallbacks/onRejectedCallbacks)。等状态敲定时依次弹出。 - 链式调用:
then必须返回一个新的MyPromise。新MyPromise的终值由当前then回调的返回值决定:- 回调返回普通值 → 新 Promise
fulfilled该值; - 回调返回一个
thenable/MyPromise→ 采用其状态和终值; - 回调抛错 → 新 Promise
rejected该错误。
- 回调返回普通值 → 新 Promise
- 微任务调度:浏览器没有公开的微任务 API,通常用
queueMicrotask,兼容写法回退到Promise.resolve().then(fn)或MutationObserver。这里直接使用queueMicrotask,更贴近规范语义。
完整实现
// 三种状态常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 工具:保证回调在微任务中执行
const runMicrotask = typeof queueMicrotask === 'function'
? queueMicrotask
: (cb) => Promise.resolve().then(cb);
// 解析 then 回调的返回值 x,决定下一个 promise 的状态
function resolvePromise(promise2, x, resolve, reject) {
// 1. 不能返回自身,否则死循环
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'));
}
// 2. 如果是 MyPromise 实例,采用其状态
if (x instanceof MyPromise) {
if (x.state === PENDING) {
x.then(
(v) => resolvePromise(promise2, v, resolve, reject),
(r) => reject(r)
);
} else if (x.state === FULFILLED) {
resolve(x.value);
} else {
reject(x.reason);
}
return;
}
// 3. 如果是 thenable 对象(鸭子类型兼容)
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let then;
try {
then = x.then;
} catch (err) {
// 取 then 抛错,直接 reject
return reject(err);
}
if (typeof then === 'function') {
// 防止重复调用
let called = false;
try {
then.call(
x,
(y) => {
if (called) return;
called = true;
// 递归解析,支持 thenable 嵌套
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} catch (err) {
if (called) return;
called = true;
reject(err);
}
} else {
// 普通对象,直接 fulfilled
resolve(x);
}
} else {
// 基础类型,直接 fulfilled
resolve(x);
}
}
class MyPromise {
constructor(executor) {
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state !== PENDING) return;
this.state = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach((fn) => fn());
};
const reject = (reason) => {
if (this.state !== PENDING) return;
this.state = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((fn) => fn());
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 值穿透:非函数时把值/原因往后传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === 'function'
? onRejected
: (r) => {
throw r;
};
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
runMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
};
const handleRejected = () => {
runMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
};
if (this.state === FULFILLED) {
handleFulfilled();
} else if (this.state === REJECTED) {
handleRejected();
} else {
// pending 时存入队列,等待 resolve/reject 触发
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onFinally) {
return this.then(
(value) => MyPromise.resolve(typeof onFinally === 'function' ? onFinally() : undefined).then(
() => value
),
(reason) =>
MyPromise.resolve(typeof onFinally === 'function' ? onFinally() : undefined).then(
() => {
throw reason;
}
)
);
}
// ---------- 静态方法 ----------
static resolve(value) {
// 已经是 MyPromise 直接返回
if (value instanceof MyPromise) return value;
// thenable 转换
if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
const then = value.then;
if (typeof then === 'function') {
return new MyPromise((resolve, reject) => {
then.call(value, resolve, reject);
});
}
}
return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const arr = Array.from(promises);
const result = new Array(arr.length);
let count = 0;
if (arr.length === 0) return resolve(result);
arr.forEach((p, i) => {
MyPromise.resolve(p).then(
(v) => {
result[i] = v;
if (++count === arr.length) resolve(result);
},
(r) => reject(r)
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
const arr = Array.from(promises);
arr.forEach((p) => {
MyPromise.resolve(p).then(resolve, reject);
});
});
}
static allSettled(promises) {
return new MyPromise((resolve) => {
const arr = Array.from(promises);
const result = new Array(arr.length);
let count = 0;
if (arr.length === 0) return resolve(result);
arr.forEach((p, i) => {
MyPromise.resolve(p).then(
(v) => {
result[i] = { status: 'fulfilled', value: v };
if (++count === arr.length) resolve(result);
},
(r) => {
result[i] = { status: 'rejected', reason: r };
if (++count === arr.length) resolve(result);
}
);
});
});
}
static any(promises) {
return new MyPromise((resolve, reject) => {
const arr = Array.from(promises);
const errors = new Array(arr.length);
let count = 0;
if (arr.length === 0) {
return reject(new AggregateError([], 'All promises were rejected'));
}
arr.forEach((p, i) => {
MyPromise.resolve(p).then(
(v) => resolve(v),
(r) => {
errors[i] = r;
if (++count === arr.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
});
}
}
边界与测试用例
// 1. 基本状态流转
const p1 = new MyPromise((resolve) => {
setTimeout(() => resolve(1), 100);
});
p1.then((v) => console.log('case1', v)); // 100ms 后输出 1
// 2. 链式调用 + 异步返回值穿透
new MyPromise((resolve) => resolve(1))
.then((v) => v + 1)
.then((v) => new MyPromise((r) => r(v * 10)))
.then((v) => console.log('case2', v)); // 20
// 3. 错误冒泡到 catch
new MyPromise((_, reject) => reject('err'))
.then((v) => console.log('不会执行', v))
.catch((e) => console.log('case3', e)); // err
// 4. finally 不吞掉原值,且其返回值不影响链
new MyPromise((r) => r(42))
.finally(() => console.log('case4 finally'))
.then((v) => console.log('case4', v)); // 42
// 5. all:其中一个失败整体失败
MyPromise.all([MyPromise.resolve(1), MyPromise.resolve(2)]).then((v) =>
console.log('case5', v)
); // [1, 2]
// 6. allSettled:保留每个结果
MyPromise.allSettled([
MyPromise.resolve(1),
MyPromise.reject('x'),
]).then((v) => console.log('case6', v));
// [{status:'fulfilled',value:1},{status:'rejected',reason:'x'}]
// 7. race:第一个落定者决定结果
MyPromise.race([
new MyPromise((r) => setTimeout(() => r('slow'), 100)),
MyPromise.resolve('fast'),
]).then((v) => console.log('case7', v)); // fast
// 8. any:第一个 fulfilled 胜出,全失败才 reject
MyPromise.any([
MyPromise.reject('a'),
MyPromise.resolve('b'),
]).then((v) => console.log('case8', v)); // b
// 9. 值穿透:onFulfilled 不是函数时,值应透传
MyPromise.resolve(7)
.then()
.then((v) => console.log('case9', v)); // 7
// 10. 循环引用检测
const p = new MyPromise((r) => r(1));
const bad = p.then(() => bad);
bad.catch((e) => console.log('case10', e.constructor.name)); // TypeError
需要特别留意的几个边界:
resolve/reject必须只生效一次,第二次调用直接忽略。then回调必须是异步执行(微任务),不能同步触发,否则 会让依赖顺序的代码出错。- 链式调用中如果
then回调返回的是当前then返回的新 Promise 自身,要抛TypeError。 all接收空数组应直接resolve([]);race接收空数组则永远pending(与原生一致)。any全部 reject 时返回AggregateError,注意老环境 polyfill。
进阶追问
- 为什么
then要放进微任务而不是宏任务? 微任务在当前同步代码执行完后、下一次事件循环前执行,时序更可控;宏任务会延迟到下一轮,导致then链路顺序与原生不一致。 then回调里return this会怎样? 会触发promise2 === x检测,抛TypeError,避免无限递归。- 如何兼容 Promise/A+ 规范的 thenable? 用
x.then鸭子类型判断 +called标志位防止 thenable 重复 resolve/reject,符合规范的 2.3.3 节。 finally为什么要Promise.resolve(onFinally())再then? 因为onFinally可能返回 Promise,需等其落定后再恢复原值/原因,保证异步行为一致。- 手写 Promise 的 executor 同步抛错会怎样? 必须
try/catch包裹 executor,捕获后调用reject,否则会同步向上抛错破坏链式调用。 - 多次调用
then的回调顺序? 同一个 Promise 上多次then注册的回调按注册顺序依次执行(规范要求 FIFO)。