
本文详解为何在迭代 Generator 返回的 Promise 时,reject 日志常晚于预期输出,并通过对比 .then().catch() 与 .then(onFulfilled, onRejected) 的执行机制,揭示 Promise 错误传播的本质。
本文详解为何在迭代 generator 返回的 promise 时,`reject` 日志常晚于预期输出,并通过对比 `.then().catch()` 与 `.then(onfulfilled, onrejected)` 的执行机制,揭示 promise 错误传播的本质。
在 JavaScript 异步编程中,一个常见却易被忽视的陷阱是:对同一个 Promise 同时调用 .then().catch() 与单独 .catch(),会导致行为差异——这正是你观察到 Rejected: B 总是最后打印的根本原因。
让我们回顾原始代码的问题所在:
for (const promise of generator) {
promise
.then((value) => console.log("Resolved:", value))
.catch((error) => console.log("Rejected:", error)); // ❌ 错误:这是在捕获 .then() 返回的新 Promise 的 rejection!
}关键点在于:.then() 总是返回一个新的 Promise。当原 Promise 被 reject(如 "B"),第一个 .then() 不会执行(因无 onRejected 参数),于是该链上的新 Promise 立即进入 rejected 状态;而后续 .catch() 实际监听的是这个衍生 Promise 的 rejection,而非原始 Promise.reject("B")。
因此,三个 Promise 的执行时序如下(按微任务队列调度):
- Promise.resolve("A") → .then() 执行 → 输出 Resolved: A → 新 Promise resolved → .catch() 被忽略
- Promise.reject("B") → .then() 跳过 → 新 Promise 立即 rejected → .catch() 在本轮微任务末尾触发 → 输出 Rejected: B
- Promise.resolve("C") → .then() 执行 → 输出 Resolved: C
但由于微任务队列的 FIFO 特性,"B" 的 rejection 处理被排在 "C" 的 resolve 之后 —— 导致最终输出为:
Resolved: A Resolved: C Rejected: B
✅ 正确做法一:使用 .then(onFulfilled, onRejected) 双参数形式
这是最简洁、语义最清晰的修复方式,直接在原始 Promise 上注册两个处理分支:
for (const promise of generator) {
promise.then(
(value) => console.log("Resolved:", value), // 成功时执行
(error) => console.log("Rejected:", error) // 失败时执行(不创建新链)
);
}✅ 优势:无额外 Promise 链,无未处理 rejection 报警,输出严格按 yield 顺序:
Resolved: A Rejected: B Resolved: C
✅ 正确做法二:分离 .then() 与 .catch() 到同一原始 Promise
for (const promise of generator) {
promise.then((value) => console.log("Resolved:", value));
promise.catch((error) => console.log("Rejected:", error));
}⚠️ 注意:此时 .then() 链若未处理 rejection,会抛出 UnhandledPromiseRejectionWarning(Node.js)或控制台警告(浏览器)。为避免警告,可添加空 catch:
promise
.then((value) => console.log("Resolved:", value))
.catch(() => {}); // 吞掉该链的 rejection,由下方独立 .catch 处理
promise.catch((error) => console.log("Rejected:", error));✅ 推荐做法三:使用 await + try/catch(现代、可读性强)
Generator + async/await 组合更符合直觉,错误流完全同步化:
(async () => {
for (const promise of generator) {
try {
const value = await promise;
console.log("Resolved:", value);
} catch (error) {
console.log("Rejected:", error);
}
}
})();✅ 输出顺序确定,无微任务调度干扰,且自动抑制未处理 rejection。
? 核心总结
| 方式 | 是否保证顺序 | 是否产生未处理 rejection | 推荐度 |
|---|---|---|---|
| .then().catch()(单参数) | ❌(错位) | ✅(警告风险) | ⚠️ 不推荐 |
| .then(onFul, onRej) | ✅ | ❌ | ⭐ 首选(轻量、精准) |
| 分离 .then() & .catch() | ✅(需补空 catch) | ⚠️(需手动抑制) | ✅ 实用 |
| await + try/catch | ✅ | ❌ | ⭐⭐ 最佳实践(ES2017+) |
? 提示:所有方案均作用于原始 Promise 实例。永远记住 —— .then() 是 Promise 转换器,不是“原地处理”,除非显式提供 onRejected,否则 rejection 必然穿透并生成新 rejected Promise。
掌握这一机制,不仅能解决 Generator 中的 Promise 顺序问题,更是写出健壮异步逻辑的底层基石。










