第3章 异步编程与错误处理

核心命题:回调地狱 → Promise 链 → async/await 演进路径;错误传播机制(冒泡 vs 显式捕获);并发控制与异常兜底。面试中通常以「手写场景 + 异常边界」组合考察。
异步错误处理回答框架:三层递进 回调地狱 嵌套 + 错误盲区 Promise 链 .then/.catch 链式 async/await 同步式异步 + try/catch 错误处理分支设计 全局未捕获 process.on('uncaughtException') Promise 未处理 process.on('unhandledRejection') 业务兜底 retry / fallback / sentry 加分点:说出各个方案的错误丢失场景 & 如何用 async迭代器 替代批量 Promise.all

面试问答卡 1:回调地狱及解决方案

面试问题请解释什么是回调地狱?你如何解决它?
考察点对异步嵌套问题的认知;是否能给出 Promise、async/await 等具体改造方案。
回答框架①定义回调地狱(嵌套+缩进爆炸+错误丢失)→②核心问题:可读性差、错误传播困难 →③解决方案:先重构为 Promise 链,再优化为 async/await + try/catch →④额外提工具:async 库、co、或 @supercharge/promise-pool。
示例回答“回调地狱指多层回调嵌套导致的代码横向膨胀。我一般先提取函数名用 Promise 包裹,然后用 async/await 改写,配合 try/catch 统一捕获错误。若大量并发,使用 Promise.allSettled 替代 Promise.all 防止单次失败全部终止。”
可能追问Promise.all 与 Promise.allSettled 区别?async/await 内部如何被编译?
避坑提醒不要只讲理论,需举具体业务场景(如数据库事务、文件读取)。避免说出“await 会阻塞”这种错误表述。

面试问答卡 2:async/await 错误捕获策略

面试问题async/await 中如何全面捕获异步错误?请手写一个可靠模式。
考察点try/catch 覆盖范围;全局未捕获处理;是否了解 Promise 错误反向冒泡。
回答框架①每个函数内 try/catch 包裹 await →②最外层添加 process.on('unhandledRejection') →③若需更高可靠性,使用 withTry 高阶函数包装(async fn => { try { return [null, await fn()] } catch(e) { return [e] } }) →④生产环境配合 sentry 上报。
示例回答“我的常用模式:①函数内用 try/catch 包围所有 await;②全局设置 unhandledRejection 监听;③编写一个 safeAwait 工具函数返回 [err, data] 元组。这样既能局部处理也能全局兜底,避免未捕获 Promise 错误导致进程崩溃。”
可能追问如何设计重试机制?如何区分业务错误与系统错误?
避坑提醒不要在 async 函数内部遗漏 catch;注意 process.on('uncaughtException') 会阻止进程退出但可能导致内存泄漏。

面试问答卡 3:Promise 链错误传播与中断

面试问题Promise 链中某个 .then 抛出错误后后续 .then 还会执行吗?如何中断链?
考察点Promise 错误冒泡机制;catch 位置的影响;链式调用控制。
回答框架①错误发生后跳过所有后续 .then,直到最近的 .catch →②如果在 .catch 后继续 .then,可恢复链 →③想中断:在 .catch 中 return Promise.reject() 或 throw 错误 →④使用 reject 后不能通过 throw 继续回调(需特殊处理)。
示例回答“错误会在 Promise 链中跳过所有 .then 直到 catch。若需要在 catch 后停止,应 return Promise.reject(new Error()) 或直接 throw。注意 finally 总会执行,适合清理资源。我曾在批量文件上传项目中利用这一特性做失败重试。”
可能追问Promise.finally 与 try/catch/finally 的区别?如何同时捕获多个 Promise 的错误?
避坑提醒勿把 .catch 放在链中间又缺少后续处理;使用 Promise.all 时其中一个失败会导致所有结果丢失,改用 allSettled 按需处理。

项目案例:高并发订单处理服务

项目名称异步订单合规校验与结算引擎
业务背景电商平台每笔订单需经过 5~8 个异步合规校验(风控、库存、价格、优惠券、物流),单日峰值订单量 80 万笔。
规模指标并发 2000+ QPS;每个校验请求平均耗时 150ms;全链路超时阈值 2s。
技术栈Node.js 16 + Express + MySQL + Redis + RabbitMQ + axios(下游RPC)
核心难点①并发校验时任一失败需快速回滚并释放资源;②超时控制与重试隔离;③防止回调地狱,错误日志可追溯。
面试高频追问“你怎么避免一个校验失败导致所有等待的 Promise 内存泄漏?”“如何处理部分失败但不想重试的非关键校验?”
可迁移回答话术“我用 Promise.allSettled + 超时 Promise.race 处理并发校验。将关键校验放在一个组内(全部成功才继续),非关键校验单独处理并记录错误。异步错误通过统一 wrap 函数转为 [err, data] 格式,结合全局 unhandledRejection 日志上报,确保生产环境零未捕获异常。”
💡 讲师提示:本项目中「Promise.allSettled + 超时 Promise.race」模式是业界标准做法,面试时主动提出会比只说 Promise.all 更具竞争力。
⚠️ 高频陷阱:面试官常问“如何中断 async/await 中的异步操作”,注意 async/await 本身不支持中断,需借助 AbortController 或提前 resolve 一个 Promise 来模拟。