1. 从 Promise 到 async/await 的错误处理全景
在现代 JavaScript 的异步编程中,Promise 的错误传播机制是理解后续迁移到 async/await 的基础。掌握正确的错误传递路径,能够避免未处理的拒绝(unhandled rejection)以及难以追踪的异步异常。
通过对比,我们可以看到 Promise 链中的异常沿着 .then 和 .catch 的组合向下传递,最终在某个环节被捕获或未被捕获时引发错误栈的中断。这一特性决定了在把代码迁移到 async/await 时,错误处理的结构需要从“把事情放到后面”改为“尽早捕获并提供上下文信息”。
1.1 Promise 的错误传播机制
错误通过 reject 传递,上传到链式回调的最后一个捕获点才会落地处理。若链中的某个 then 内部抛出异常,这个异常会被后续的 catch 捕获,但如果链路中没有 catch,错误将变成未处理的拒绝。
下面的示例展示了如何在 Promise 链中显式捕获错误,并在需要时重新抛出以提供上下文信息:
function fetchData() {return fetch('/api/data').then(res => res.json()).catch(err => {// 给错误增加上下文再抛出throw new Error('fetchData 失败: ' + err.message);});
}fetchData().then(data => console.log(data)).catch(err => console.error('捕获到错误:', err.message));
1.2 链式捕获与错误上下文传递
在复杂的 Promise 链中,避免把错误丢到链尾才处理,而是通过在关键节点增加上下文信息来提升可诊断性。为了保持错误的可追踪性,常见做法是把错误再包装成带有代码码或场景描述的新错误,并继续抛出。
接下来展示一个带有上下文信息的错误包装模式,便于上层调用者快速定位问题来源:
function readConfig() {return Promise.resolve({});// 假设这是读取配置的异步操作
}
readConfig().then(cfg => {if (!cfg.url) throw new Error('缺失必要字段: url');return cfg.url;}).catch(err => {throw new Error('读取配置失败:' + err.message);}).then(url => console.log('URL:', url)).catch(err => console.error('最终错误:', err.message));
2. Promise 链中的错误处理技巧
2.1 尾部捕获与中间捕获的取舍
在 Promise 链中,尾部 catch 能统一处理所有下游异常,而在中间某个节点进行捕获并重新抛出,可以为该节点添加额外的错误信息,提升可观测性。

示例中展示了两种策略:直接在末端捕获,或在中间节点添加上下文后再继续传播:
// 尾部捕获
fetch('/api') .then(r => r.json()).then(data => console.log(data)).catch(err => console.error('统一处理错误:', err.message));// 中间捕获后继续传播
fetch('/api').then(r => r.json()).catch(err => { throw new Error('解析失败前置阶段: ' + err.message); }).then(data => console.log(data)).catch(err => console.error('最终错误:', err.message));
2.2 错误上下文的上下文化与日志化
为了实现更易于定位的错误诊断,将错误上下文注入到错误对象中,并在日志中输出关键字段,是提升可维护性的常用做法。
下面给出一个上下文化错误的实现范式:
function withContext(promise, context) {return promise.catch(err => {const enhanced = new Error(`${context} - ${err.message}`);enhanced.original = err;throw enhanced;});
}withContext(fetch('/api'), '获取数据阶段').then(r => r.json()).then(data => console.log(data)).catch(err => console.error(err.message));
3. 转向 async/await 的实战要点
3.1 使用 try/catch 捕获异步错误
将 Promise 链平移到 async/await 时,try/catch 成为最直接的错误捕获手段。注意不要在纯异步逻辑中遗漏 try/catch,否则错误会被忽略。
一个基本的异步请求封装示例,展示如何在等待阶段统一处理错误:
async function loadUser() {try {const res = await fetch('/api/user');const user = await res.json();return user;} catch (err) {throw new Error('加载用户失败: ' + err.message);}
}loadUser().then(u => console.log(u)).catch(err => console.error(err.message));
3.2 多个并发请求的错误处理
面对多个并发异步操作,Promise.all 提供并发执行能力,但如果任意一个失败,整个集合都将失败。因此,需要明确的错误处理策略,例如捕获并在外层进行统一管理。
下面示例展示了在 async/await 场景下对并发请求的错误处理:
async function loadAll(urls) {try {const results = await Promise.all(urls.map(u => fetch(u).then(r => r.json())));return results;} catch (err) {throw new Error('并发请求失败: ' + err.message);}
}
3.3 finally 的清理与资源释放
在异步流程中,不论成功或失败,finally 块都适合用来释放资源、清理环境,确保后续步骤不受前面阶段状态影响。
示例体现了在异步流程中如何执行清理操作:
async function processResource() {let resource;try {resource = await acquireResource();// 进行核心处理await doWork(resource);} catch (err) {throw err;} finally {if (resource) {await releaseResource(resource);}}
}
4. 统一的错误处理策略与可维护性
4.1 全局错误处理入口
为提升可观测性,可以建立一个中央的错误处理入口,用来统一记录、分类和告警;这在复杂系统中尤其重要,能显著降低重复处理成本。
示例展示了一个简单的中央处理函数,以及主流程中的调度点:
function handleError(err) {console.error('[Error]', err?.message || err);// 可能触发告警系统,例如 sending to SRE
}async function main() {try {await doWork();} catch (err) {handleError(err);}
}
4.2 自定义错误类型以便分层处理
通过定义自定义错误类型,可以让错误具备代码、等级等元信息,便于在不同层次上做有针对性的处理。
一个简单的自定义错误类型示例,便于在 catch 块中区分错位的源头并做特定处理:
class AppError extends Error {constructor(message, code) {super(message);this.code = code;}
}throw new AppError('Invalid user', 'USER_INVALID');


