为何Promise.all前的Promise拒绝未被捕获?Node.js事件循环疑问
为什么Promise.reject的错误有时无法被try/catch捕获?
问题重现
先看这段简化后的代码:
const waitResolve = (ms) => new Promise((resolve) => { setTimeout(() => { console.log(`Waited to resolve for ${ms}ms`); resolve(ms); }, ms); }); const waitReject = (ms) => new Promise((resolve, reject) => { setTimeout(() => { console.log(`Waited to reject for ${ms}ms`); reject(ms); }, ms); }); const run = async() => { const promises = { a: [], b: [], c: [], }; for (let i = 0; i < 5; i += 1) { promises.a.push(waitResolve(1e4)); promises.b.push(waitReject(1e3)); promises.c.push(waitResolve(1e2)); } try { for (const [key, value] of Object.entries(promises)) { console.log(`Starting ${key}`); try { await Promise.all(value); } catch (err) { console.log(`Caught error in ${key}!`, err); } console.log(`Finished ${key}`); } } catch (err) { console.log('Caught error in run!', err); } }; run();
运行这段代码会发现:waitReject(1e3)的拒绝并没有被内层的try/catch捕获,反而直接抛出到全局导致程序崩溃。但如果调整Promise的创建顺序如下,错误就能正常被捕获:
promises.a.push(waitResolve(1e2)); promises.b.push(waitReject(1e3)); promises.c.push(waitResolve(1e4));
核心原因解析
1. 纠正一个普遍误区:Promise创建即执行
很多开发者误以为Promise要等到await或Promise.all调用才会开始执行,这完全错误。当你调用waitResolve()或waitReject()的瞬间,Promise就已经被实例化,内部的setTimeout已经被推入事件循环的宏任务队列——后续的await Promise.all只是监听这些已经在运行的Promise的状态,根本不是“启动”它们的触发器。
2. Node.js事件循环的执行优先级(简化版)
事件循环的执行顺序遵循以下规则:
- 先执行完当前所有同步代码
- 清空当前微任务队列(比如Promise的
resolve/reject回调、process.nextTick等) - 再执行下一个宏任务(比如
setTimeout、setInterval等)
3. 第一种情况(错误无法捕获)的时序拆解
原代码中,Promise的创建顺序是:
- 5个
waitResolve(1e4)(10秒后resolve) - 5个
waitReject(1e3)(1秒后reject) - 5个
waitResolve(1e2)(0.1秒后resolve)
具体执行流程:
- 同步代码阶段:循环创建所有Promise,三个类型的
setTimeout依次进入宏任务队列,延时分别为10000ms、1000ms、100ms - 进入
for-of循环,打印Starting a,然后执行await Promise.all(promises.a)——主线程挂起,等待promises.a的所有Promise完成 - 0.1秒后,
waitResolve(1e2)的宏任务触发:打印日志并resolve Promise,对应的微任务(Promise.all的完成回调)被加入微任务队列,但此时主线程还在等待promises.a的Promise,微任务无法执行 - 1秒后,
waitReject(1e3)的宏任务触发:打印日志并reject Promise。此时这些Promise还没有被任何错误处理逻辑监听(因为我们还没走到处理promises.b的代码),这属于未处理的Promise拒绝,Node.js会直接将其抛出到全局,程序崩溃,后续处理promises.b的代码根本没机会执行
4. 第二种情况(错误可以捕获)的时序拆解
调整顺序后,Promise的创建顺序是:
- 5个
waitResolve(1e2)(0.1秒后resolve) - 5个
waitReject(1e3)(1秒后reject) - 5个
waitResolve(1e4)(10秒后resolve)
具体执行流程:
- 同步代码阶段:循环创建所有Promise,
setTimeout依次进入宏任务队列,延时为100ms、1000ms、10000ms - 进入
for-of循环,打印Starting a,执行await Promise.all(promises.a)——主线程挂起 - 0.1秒后,
waitResolve(1e2)的宏任务触发:resolve Promise,微任务队列加入Promise.all的完成回调 - 主线程从
await恢复,执行微任务,Promise.all(promises.a)完成,打印Finished a - 进入下一个循环,打印
Starting b,执行await Promise.all(promises.b)——此时我们已经通过try/catch给这些Promise加上了错误监听 - 1秒后,
waitReject(1e3)的宏任务触发:reject Promise,微任务队列加入Promise.all的拒绝回调 - 主线程从
await恢复,执行微任务,触发catch块,打印Caught error in b!,然后打印Finished b,后续代码正常执行
总结
- Promise实例化后立即执行,
await和Promise.all只是监听状态,不是启动Promise的开关 - 未处理的Promise拒绝会直接导致程序崩溃,必须确保Promise被拒绝时,对应的错误处理逻辑已经准备就绪
- 这个问题的本质是:Promise拒绝发生的时机,早于错误处理逻辑的执行时机,导致拒绝变成未处理状态
内容的提问来源于stack exchange,提问作者Damaged Organic
相关产品推荐
相关产品推荐

