JavaScript 如何捕获异步错误
平时写代码时,你肯定遇到过这种情况:调用一个接口时明明参数没问题,却突然弹出“请求失败”的提示;或者给用户的操作反馈,明明应该显示“成功”,结果却跳出了莫名其妙的错误。这些大多和异步代码的错误没被正确捕获有关。
我们先从最贴近生活的例子讲起:你点外卖时,平台会同时处理“接单”“备餐”“配送”三件事,这就是异步操作——不用等一件事做完再做下一件。如果配送的路上堵车了(相当于异步代码出了错),你不能一直盯着配送员等他解决问题,而是需要有个“售后”来处理这个突发状况,否则你会一直等不到外卖,系统也会乱套。

JavaScript 里的异步代码,常见的有两种写法:一种是老派的回调函数,比如 fs.readFile(path, callback);另一种是现在常用的 Promise 链式调用,或者 async/await 语法。不同的异步写法,捕获错误的方式也不一样。
用 try/catch 捕获同步代码的误区
很多人刚学 async/await 的时候,会直接在外面套个 try/catch,以为所有错误都能兜住。但这是个典型的误区:try/catch 只能捕获同步代码的错误,对异步的 Promise 无效。比如下面的代码,await 本身是异步的,错误会“跳”出当前的同步上下文,try/catch 根本抓不到:
`javascript
try {
setTimeout(() => {
throw new Error("外卖送错了"); // 这个错误永远抓不到
}, 1000);
} catch (err) {
console.log("错误被捕获:", err); // 这里永远不会执行
}
`
这就像你盯着外卖员等取餐,结果他半路跑去刷手机,你当然等不到他出问题的消息——setTimeout 里的回调会被放到异步队列,try/catch 已经执行完了,自然抓不住。
Promise 的错误捕获:.catch() 方法
如果用 Promise 做异步操作,它自带的 .catch() 就是专门的“售后岗”,不管是 Promise 内部 throw 的错误,还是异步操作失败的拒绝状态,都能被它接住。还是用外卖的例子:如果平台承诺“30分钟送到”(这是个 Promise),没送到就会触发“退款”(对应 .catch()),而不是让用户自己找问题:
`javascript
new Promise((resolve, reject) => {
// 模拟配送延迟
setTimeout(() => {
reject(new Error("配送超时,超过30分钟"));
}, 35 * 60 * 1000);
}).catch(err => {
console.log("捕获到异步错误:", err.message); // 输出“捕获到异步错误:配送超时,超过30分钟”
});
`
哪怕是链式调用的多个 Promise,错误也会一直向后传递,直到遇到 .catch() 才停下,就像外卖出问题后,不管中间经过了多少环节,最终都会到用户的售后渠道处理。
async/await 正确捕获异步错误的姿势
既然 try/catch 不能直接套在 async/await 外面,那怎么用它捕获错误?答案是把 await 单独放在 try 块里,就像你要盯着外卖员取餐的那个环节,盯着他有没有出问题:
`javascript
async function getTakeout() {
try {
// 把异步操作放在 try 里,让 await 等待它的结果
const result = await new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("外卖送错了,不是你点的餐"));
}, 1000);
});
console.log("取餐成功:", result);
} catch (err) {
// 这时候就能捕获到 await 对应的 Promise 错误了
console.log("正确捕获:", err.message); // 输出“正确捕获:外卖送错了,不是你点的餐”
}
}
getTakeout();
`
这个写法就像你专门留了个位置盯着外卖员的配送环节,他出问题就会立刻被你发现,而不是等你转而去做别的事再回来找。
另外还要注意,如果你用的是第三方库的异步方法,或者自己封装的 Promise,一定要确保它返回的是标准 Promise——如果是错误的回调写法,比如没有用 reject 而是直接 throw,那还是会有错误漏网。就像有些小作坊外卖,出问题不告诉平台,直接跑路,这种情况就需要你自己在回调里加判断,手动抛出错误。
简单来说,JavaScript 捕获异步错误的核心就是“找对对应渠道”:Promise 用 .catch(),async/await 把 await 放进 try/catch,别用同步的方法处理异步的问题,就像给外卖的不同环节配对应的售后,自然不会出乱子。