1. 题目
实现一个满足以下核心特性的迷你 MyPromise:
- 三种状态:
pending、fulfilled、rejected,状态一经改变不再变化。
- 构造器接收
executor(resolve, reject),立即执行;executor 内抛错则转为 rejected。
resolve/reject 可接收值;若 resolve 一个 Promise(thenable),要等它 settle 后再决定本 Promise 状态。
then(onFulfilled, onRejected) 支持链式调用(返回新 Promise),且回调异步在微任务里执行;支持值穿透(回调不是函数时透传)。
catch 复用 then。
2. 解题思路
四个关键点:
- 状态机:用一个
status 字段 + 只允许一次变更(用 changeStatus 守卫),保证不可逆。
- 异步执行回调:
then 的回调不能立即调用(可能在 resolve 之前就被 then),统一丢进 queueMicrotask(模拟微任务,等价 Promise.resolve().then())。
- 链式 & 值穿透:
then 永远返回一个新的 Promise;若传入的不是函数,用默认的 v => v(成功透传)和 err => throw err(失败下抛),从而支持 .then().then() 跳过。
- resolve thenable:
resolve 收到的若是带 then 方法的对象/ Promise,需递归地采用它的最终状态,不能直接 fulfilled。
- 状态变更 O(1);链式每个 then O(1)。
3. TypeScript 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| type Status = "pending" | "fulfilled" | "rejected";
class MyPromise { private status: Status = "pending"; private value: any = undefined; private reason: any = undefined;
constructor(executor: (resolve: (v?: any) => void, reject: (r?: any) => void) => void) { try { executor(this.resolve.bind(this), this.reject.bind(this)); } catch (e) { this.reject(e); } }
private resolve(value: any): void { if (this.status !== "pending") return; if (value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function") { value.then(this.resolve.bind(this), this.reject.bind(this)); return; } this.status = "fulfilled"; this.value = value; }
private reject(reason: any): void { if (this.status !== "pending") return; this.status = "rejected"; this.reason = reason; }
then(onFulfilled?: any, onRejected?: any): MyPromise { onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v: any) => v; onRejected = typeof onRejected === "function" ? onRejected : (e: any) => { throw e; };
return new MyPromise((resolve, reject) => { const runFulfilled = () => queueMicrotask(() => { try { resolve(onFulfilled(this.value)); } catch (e) { reject(e); } }); const runRejected = () => queueMicrotask(() => { try { resolve(onRejected(this.reason)); } catch (e) { reject(e); } });
if (this.status === "fulfilled") { runFulfilled(); } else if (this.status === "rejected") { runRejected(); } }); }
catch(onRejected: any): MyPromise { return this.then(undefined, onRejected); }
static resolve(v: any): MyPromise { if (v instanceof MyPromise) return v; return new MyPromise((resolve) => resolve(v)); } }
new MyPromise<number>((resolve) => setTimeout(() => resolve(1), 50)) .then((v) => { console.log(v); return v + 1; }) .then(null) .then((v) => console.log(v));
|
说明:为了讲解聚焦,上面 then 省略了「pending 状态下回调收集」的实现。完整版本必须在 Promise 尚未 settle 时把回调 push 进 onFulfilledCbs[] / onRejectedCbs[],并在 resolve/reject 里遍历触发——这是异步 executor 场景正确性的关键。
4. 面试延伸
- 为什么用微任务而不是
setTimeout:Promise 回调属微任务,在同步代码之后、下一轮事件循环前统一执行;setTimeout 是宏任务,时序不同。能讲清宏/微任务队列即是加分。
then 返回新 Promise 且 resolve 回调返回值:正是这个「返回值再交给新 Promise 的 resolve」实现了链式传递和 thenable 展开。
resolve(new MyPromise(...)) 不会立刻定状态:因为 thenable 递归采用逻辑,能答出这点说明真懂。
- 进阶补全:加上 pending 回调队列、静态
all/race/any/allSettled(见 [Promise.all]、[Promise.race] 两篇),即接近 A+ 规范。
- 异常处理:回调内
throw 会被新 Promise reject 捕获,向下游 catch 冒泡,与原生一致。
难度:困难 | 手写 Promise 核心 | 异步机制试金石