手写Promise类深度解析
从零实现符合 Promise/A+ 规范的 Promise 类——状态机、then 链式调用、resolvePromise、微任务调度,逐行拆解。
一句话概括
手写 Promise 不是炫技,是把异步编程的底层逻辑亲手摸一遍——状态机决定何时唤醒回调,then 返回新 Promise 决定链式如何连接,resolvePromise 决定返回值如何传递。
核心知识点
1. 状态机 + 回调队列 = Promise 的骨架
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
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
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 (e) { reject(e); }
}
}
pending 时回调被囤积在数组里;状态一旦落定,批量释放回调。这就是 Promise 处理异步的秘诀。
2. then 返回新 Promise —— 链式调用的灵魂
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
then(onFulfilled, onRejected) {
// 值穿透
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };
const promise2 = new MyPromise((resolve, reject) => {
const handle = (fn, val) => {
queueMicrotask(() => {
try {
const x = fn(val);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) { reject(e); }
});
};
if (this.state === FULFILLED) handle(onFulfilled, this.value);
else if (this.state === REJECTED) handle(onRejected, this.reason);
else {
this.onFulfilledCallbacks.push(() => handle(onFulfilled, this.value));
this.onRejectedCallbacks.push(() => handle(onRejected, this.reason));
}
});
return promise2;
}
关键细节:
- 返回新 Promise:
then(() => 1).then(() => 2)能链起来,是因为每个 then 都返回独立的 Promise - 值穿透:
.then()不传回调时,值原样传递;.then(null, null)不会断链 - 微任务:回调必须用
queueMicrotask异步执行,符合 Promise/A+
3. resolvePromise —— 处理 then 回调的返回值
这是手写 Promise 最复杂的部分,规范里叫 Promise Resolution Procedure:
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
resolvePromise(promise2, x, resolve, reject) {
// 防止循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
// x 是 MyPromise 实例 → 等它完成
if (x instanceof MyPromise) {
return x.then(resolve, reject);
}
// 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,
v => { if (!called) { called = true; resolve(v); } },
e => { if (!called) { called = true; reject(e); } }
);
} else {
resolve(x);
}
} catch (e) {
if (!called) reject(e);
}
return;
}
// 普通值
resolve(x);
}
三段分支:Promise → 等;thenable → 调 then 并防多次调用;普通值 → 直接 resolve。
4. catch / finally
1
2
3
4
5
6
7
8
9
10
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
}
catch 是 then(null, fn) 的语法糖。finally 关键是透传:原来的 value/reason 不变,只额外执行 callback。
5. 静态方法
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
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(r => r(value));
}
static reject(reason) {
return new MyPromise((_, r) => r(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
if (promises.length === 0) return resolve([]);
promises.forEach((p, i) => {
MyPromise.resolve(p).then(v => {
results[i] = v; // 保持顺序
if (++count === promises.length) resolve(results);
}, reject); // 任一失败立即 reject
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(p => MyPromise.resolve(p).then(resolve, reject));
});
}
static allSettled(promises) {
return new MyPromise((resolve) => {
const results = [];
let count = 0;
if (promises.length === 0) return resolve([]);
promises.forEach((p, i) => {
MyPromise.resolve(p).then(
v => { results[i] = { status: 'fulfilled', value: v }; if (++count === promises.length) resolve(results); },
e => { results[i] = { status: 'rejected', reason: e }; if (++count === promises.length) resolve(results); }
);
});
});
}
static any(promises) {
return new MyPromise((resolve, reject) => {
const errors = [];
let count = 0;
if (promises.length === 0) return reject(new AggregateError([], 'All promises were rejected'));
promises.forEach((p, i) => {
MyPromise.resolve(p).then(resolve, e => {
errors[i] = e;
if (++count === promises.length) reject(new AggregateError(errors, 'All promises were rejected'));
});
});
});
}
「其实你每天都在用」
1. Node.js 的 fs.promises
1
2
const fs = require('fs/promises');
await fs.readFile('a.txt'); // 内部返回 Promise
2. axios / fetch 返回 Promise
1
axios.get('/api').then(r => r.data); // axios 内部 new Promise(...)
3. React.lazy
1
2
const Lazy = React.lazy(() => import('./Heavy'));
// import() 返回 Promise,lazy 内部等它 resolve
4. Web API:navigator.clipboard
1
navigator.clipboard.writeText('hello').then(() => console.log('已复制'));
5. 所有 async 函数
每个 async function 的返回值都经过我们手写的这一套逻辑。
常见误解(FAQ)
❌ 误区 1:「then 返回 this 就行」
不行。如果返回 this,链上的状态就共享了——p.then(() => 'a').then(() => 'b') 第二个 then 收到的不是 ‘a’,而是原始 Promise 的值。这就是为什么必须返回新 Promise。
❌ 误区 2:「resolvePromise 里判断 thenable 只是锦上添花」
这是规范要求的核心逻辑,不是可选的。如果你的 Promise resolve 了一个 jQuery 的 Deferred、或者另一个 Promise 库的实例,必须能正确处理。否则跨库互操作直接崩。
❌ 误区 3:「用 setTimeout 模拟微任务也行」
会破坏执行顺序。Promise.then 如果走 setTimeout(宏任务),输出顺序就和原生 Promise 不一致。面试官一眼看穿。
❌ 误区 4:「手写 Promise 只需要实现 then」
catch、finally、all、race、resolve、reject 都是常见追问。Promise.allSettled 和 Promise.any 是进阶加分项。
一句话总结
手写 Promise 真正在写的不是代码,是状态流转的纪律——何时等、何时调、何时传,每一步都在训练你对异步的掌控力。