0%

从零实现 Promise——异步编程原理解析

手写 Promise 的过程就像烹饪一道复杂的菜肴,需要耐心和细致的步骤。通过这篇文章,你将学会如何从零开始实现一个符合 Promise/A+规范的 Promise,深入理解其内部机制和工作原理。准备好了吗?让我们一起动手吧!

介绍

  Promise 是 Javascript 中处理异步操作的重要概念,它代表了一个异步操作的最终完成(或失败)及其结果值。今天我们将从零开始手写一个符合 Promise/A+ 规范的 Promise,深入了解其内部实现机制。

Promise/A+ 规范解读

  在开始实现之前,我们先了解一下 Promise/A+ 规范的核心要求:

  1. Promise 有三个状态pending(等待)、fulfilled(完成)、rejected(拒绝)
  2. 状态只能从 pending 转变为 fulfilled 或 rejected,且只能改变一次
  3. Promise 必须有一个 then 方法,用于注册回调

基础版本实现

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
76
77
78
79
80
81
82
83
84
85
86
87
88
function MyPromise(executor) {
// 定义初始状态 this.state = 'pending';
// 存储成功结果 this.value = undefined;
// 存储失败原因 this.reason = undefined;

// 成功回调队列 this.onResolvedCallbacks = [];
// 失败回调队列 this.onRejectedCallbacks = [];

// 成功时执行的函数 const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;

// 执行所有成功的回调 this.onResolvedCallbacks.forEach(fn => fn());
}
};

// 失败时执行的函数 const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;

// 执行所有失败的回调 this.onRejectedCallbacks.forEach(fn => fn());
}
};

// 立即执行 executor 函数 try {
executor(resolve, reject);
} catch (error) {
// 如果 executor 执行过程中出错,直接拒绝 Promise
reject(error);
}
}

// 定义 then 方法 MyPromise.prototype.then = function (onFulfilled, onRejected) {
// 参数可选,如果未提供则使用默认函数 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

// 创建新的 Promise 实例 const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 如果当前 Promise 已经成功,立即执行成功回调 setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}

if (this.state === 'rejected') {
// 如果当前 Promise 已经失败,立即执行失败回调 setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}

if (this.state === 'pending') {
// 如果当前 Promise 还在等待状态,将回调函数存储起来 this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});

this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});

return promise2;
};

核心解析函数

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
/**
* 解析 Promise 返回值的函数
* @param {MyPromise} promise2 返回的新 Promise 实例
* @param {*} x onFulfilled 或 onRejected 的返回值
* @param {Function} resolve promise2 的 resolve 方法
* @param {Function} reject promise2 的 reject 方法
*/
function resolvePromise(promise2, x, resolve, reject) {
// 防止循环引用 if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}

let called = false; // 防止多次调用 resolve 或 reject

if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// x 可能是 Promise 或其他 Thenable 对象 try {
const then = x.then; // 取出 x 的 then 方法 if (typeof then === 'function') {
// x 是 Thenable 对象 then.call(x, y => {
if (called) return;
called = true;
// 递归解析,直到解析出普通值 resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true;
reject(r);
});
} else {
// x 是普通对象 if (called) return;
called = true;
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
// x 是普通值 resolve(x);
}
}

完善 Promise 功能

添加 catch 方法

1
2
3
MyPromise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};

添加 finally 方法

1
2
3
4
5
6
MyPromise.prototype.finally = function (callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
};

添加静态方法

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// 静态 resolve 方法 MyPromise.resolve = function (value) {
if (value instanceof MyPromise) {
return value;
}

return new MyPromise(resolve => resolve(value));
};

// 静态 reject 方法 MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => reject(reason));
};

// 静态 all 方法 MyPromise.all = function (promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;

if (promises.length === 0) {
resolve(results);
return;
}

promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(value => {
results[index] = value;
count++;
if (count === promises.length) {
resolve(results);
}
}, reject);
});
});
};

// 静态 race 方法 MyPromise.race = function (promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
};

// 静态 allSettled 方法 MyPromise.allSettled = function (promises) {
return new MyPromise(resolve => {
const results = [];
let count = 0;

if (promises.length === 0) {
resolve(results);
return;
}

promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(value => {
results[index] = { status: 'fulfilled', value };
count++;
if (count === promises.length) {
resolve(results);
}
}, reason => {
results[index] = { status: 'rejected', reason };
count++;
if (count === promises.length) {
resolve(results);
}
});
});
});
};

// 静态 any 方法 MyPromise.any = function (promises) {
return new MyPromise((resolve, reject) => {
const errors = [];
let count = 0;

if (promises.length === 0) {
reject(new AggregateError('All promises were rejected'));
return;
}

promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(resolve, reason => {
errors[index] = reason;
count++;
if (count === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
};

完整实现与测试

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// 将上面的所有代码组合在一起 function MyPromise(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];

const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};

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

MyPromise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}

if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}

if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});

this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});

return promise2;
};

function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}

let called = false;

if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;

if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true;
reject(r);
});
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}

MyPromise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};

MyPromise.prototype.finally = function (callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
};

MyPromise.resolve = function (value) {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise(resolve => resolve(value));
};

MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => reject(reason));
};

MyPromise.all = function (promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;

if (promises.length === 0) {
resolve(results);
return;
}

promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(value => {
results[index] = value;
count++;
if (count === promises.length) {
resolve(results);
}
}, reject);
});
});
};

MyPromise.race = function (promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
};

MyPromise.allSettled = function (promises) {
return new MyPromise(resolve => {
const results = [];
let count = 0;

if (promises.length === 0) {
resolve(results);
return;
}

promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(value => {
results[index] = { status: 'fulfilled', value };
count++;
if (count === promises.length) {
resolve(results);
}
}, reason => {
results[index] = { status: 'rejected', reason };
count++;
if (count === promises.length) {
resolve(results);
}
});
});
});
};

MyPromise.any = function (promises) {
return new MyPromise((resolve, reject) => {
const errors = [];
let count = 0;

if (promises.length === 0) {
reject(new AggregateError('All promises were rejected'));
return;
}

promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(resolve, reason => {
errors[index] = reason;
count++;
if (count === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
};

测试手写的 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
30
31
32
33
34
35
36
37
// 测试基本功能 const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功');
}, 1000);
});

p1.then(value => {
console.log('第一个 then:', value); // 1秒后输出 "成功"
return '返回值1';
}).then(value => {
console.log('第二个 then:', value); // 再过一点时间输出 "返回值1"
throw new Error('出错了');
}).catch(error => {
console.log('捕获错误:', error.message); // 输出 "出错了"
return '错误处理后的返回值';
}).then(value => {
console.log('错误处理后:', value); // 输出 "错误处理后的返回值"
});

// 测试 Promise.all
const p2 = MyPromise.resolve(1);
const p3 = MyPromise.resolve(2);
const p4 = MyPromise.reject('失败');

MyPromise.all([p2, p3]).then(values => {
console.log('all 成功:', values); // [1, 2]
}).catch(error => {
console.log('all 失败:', error);
});

// 测试 Promise.race
const p5 = new MyPromise(resolve => setTimeout(() => resolve('慢'), 1000));
const p6 = new MyPromise(resolve => setTimeout(() => resolve('快'), 100));

MyPromise.race([p5, p6]).then(value => {
console.log('race 结果:', value); // '快'
});

链式调用的奥秘

  Promise 的链式调用是通过 then 方法返回新的 Promise 实现的。每一个 then 都会返回一个新的 Promise,这样就能实现链式调用。这是 Promise 设计中最为巧妙的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 演示链式调用的内部原理 const promise = new MyPromise(resolve => resolve(1));

promise
.then(value => {
console.log(value); // 1
return value + 1; // 返回 2
})
.then(value => {
console.log(value); // 2
return value + 1; // 返回 3
})
.then(value => {
console.log(value); // 3
});

实际应用场景

异步操作的顺序执行

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
function fetchUser(userId) {
return new MyPromise(resolve => {
setTimeout(() => {
resolve({ id: userId, name: `用户${userId}` });
}, 100);
});
}

function fetchUserPosts(userId) {
return new MyPromise(resolve => {
setTimeout(() => {
resolve([`帖子1-${userId}`, `帖子2-${userId}`]);
}, 200);
});
}

// 顺序执行异步操作 fetchUser(1)
.then(user => {
console.log('获取用户:', user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log('获取帖子:', posts);
})
.catch(error => {
console.error('错误:', error);
});

并行执行异步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
const fetchTasks = [
fetchUser(1),
fetchUser(2),
fetchUser(3)
];

MyPromise.all(fetchTasks)
.then(users => {
console.log('所有用户:', users);
})
.catch(error => {
console.error('获取用户失败:', error);
});

常见错误及注意事项

1. 循环引用

1
2
// 错误示例 - 会导致循环引用错误 const p = new MyPromise(resolve => resolve(1));
const p2 = p.then(() => p2); // 这里会报错

2. 多次调用 resolve 或 reject

1
2
3
4
// 错误示例 - resolve 和 reject 只会执行第一次 new MyPromise((resolve, reject) => {
resolve('第一次');
resolve('第二次'); // 不会被执行 reject('第三次'); // 不会被执行
});

3. 同步执行与异步执行的差异

1
2
3
4
5
6
7
// 同步 Promise
const syncPromise = new MyPromise(resolve => resolve('sync'));
syncPromise.then(value => console.log('第一个', value));
console.log('同步代码');

// 输出顺序:同步代码,然后是 第一个 sync
// 因为 then 中的回调会异步执行

总结

  • Promise 的核心是状态机,只能从 pending 转变为 fulfilled 或 rejected,且状态一旦改变就不可逆转
  • then 方法返回新的 Promise,实现了链式调用
  • resolvePromise 函数是实现 Promise/A+ 规范的关键,需要处理各种边界情况
  • Promise 解决了回调地狱问题,使异步代码更易读易维护
  • 理解 Promise 的实现原理有助于更好地使用它,避免常见错误

今天写完这篇博客,突然想到火锅串串,就像 Promise 链式调用一样,一串接一串,环环相扣。学习技术的同时,生活的灵感也在不经意间涌现,真是一举两得!

练习题

  1. 如何实现 Promise.try(func),它会调用 func(),如果 func 是一个函数的话,并返回一个 Promise,该 Promise 会解决为 func() 的返回值。
  2. 实现 Promise.delay(ms),返回一个在 ms 毫秒后 resolve 的 Promise。
  3. 实现 Promise.timeout(ms),如果原始 Promise 在 ms 毫秒内没有解决,则 reject。

参考资料

  • Promise/A+ 规范
  • MDN Promise
  • 深入理解 Promise
  • 手写 Promise 源码
bulb