JavaScript 错误处理有哪些方式?同步异常和异步异常有什么区别?
简化版
同步代码可以用 try...catch 捕获异常;Promise 异步错误用 .catch 或 async/await 外层的 try...catch 捕获;事件回调、定时器、跨任务异常不能被外层同步 try...catch 捕获。
工程上要区分预期错误和程序错误,既要给用户友好反馈,也要记录日志、保留上下文并避免吞掉错误。
详细版
同步异常:
try {
JSON.parse("{bad json}");
} catch (err) {
console.log(err.message);
}
Promise 异常:
fetch("/api/user")
.then(res => res.json())
.catch(err => report(err));
async/await:
async function loadUser() {
try {
const res = await fetch("/api/user");
return await res.json();
} catch (err) {
report(err);
throw err;
}
}
定时器里的错误不会被外层同步 try...catch 捕获,因为它在另一个任务中执行。
完整版教学
一、错误处理先分同步和异步
同步异常发生在当前调用栈中,try...catch 能捕获。
异步异常发生在未来某个任务、微任务或回调里,外层同步调用栈已经结束,不能靠外层 try...catch 兜住所有情况。
try {
setTimeout(() => {
throw new Error("timer error");
}, 0);
} catch (err) {
console.log("will not run");
}
面试答错误处理时,先说“调用栈边界”,很多异步捕获问题就清楚了。
二、try…catch 适合当前调用栈
try...catch 适合解析、计算、同步 API 调用等当前栈内错误。
function parseConfig(text) {
try {
return JSON.parse(text);
} catch (err) {
return { theme: "default" };
}
}
但不要把过大的业务流程全部包起来后静默失败。捕获后应知道怎么恢复,或者继续向上抛。
三、Promise 的错误传播
Promise 链中的 throw 会变成 rejected。
Promise.resolve()
.then(() => {
throw new Error("bad");
})
.catch(err => console.log(err.message));
.catch 本质上相当于只处理 rejected 的 .then。如果 .catch 里不重新抛出,后续链会回到 fulfilled。
Promise.reject(new Error("x"))
.catch(() => "fallback")
.then(value => console.log(value)); // fallback
四、async/await 的捕获方式
await 一个 rejected Promise 时,会像同步 throw 一样进入 catch。
async function main() {
try {
await Promise.reject(new Error("fail"));
} catch (err) {
console.log("caught");
}
}
如果忘记 await,错误不会被当前 try...catch 捕获。
try {
Promise.reject(new Error("lost"));
} catch (err) {
console.log("not caught");
}
五、全局兜底不是业务处理
浏览器里常见兜底:
| 入口 | 作用 |
|---|---|
window.onerror | 捕获部分未处理同步错误 |
unhandledrejection | 捕获未处理 Promise rejection |
| 框架错误边界 | 捕获框架渲染层错误 |
兜底适合上报和降级,不适合当作正常业务流程。
六、预期错误和程序错误要分开
预期错误是业务可以理解的失败,比如表单校验失败、接口 401、库存不足。
程序错误是代码缺陷,比如空指针、类型假设错误、状态不一致。
error
├─ expected: show message / retry / redirect
└─ bug: report / isolate / fail fast
把所有错误都弹一个“系统繁忙”会让排障困难,也会伤害用户体验。
七、常见误区与追问
- 误区:外层 try…catch 能捕获 setTimeout 里的 throw。 定时器回调在新的任务中执行,外层栈已经结束。
- 误区:Promise 里 catch 后一定还会失败。 如果不重新抛出,链会恢复为 fulfilled。
- 误区:async 函数里的错误都会自动被处理。 调用方仍然要
await或.catch。 - 误区:全局错误监听能替代局部错误处理。 全局兜底主要负责上报和最后降级。
- 追问:为什么不能随便吞掉错误? 会让调用方误以为成功,导致状态继续被污染。
- 追问:错误上报应该带哪些上下文? URL、用户操作、接口、组件栈、版本号和关键参数。
八、加强记忆
按边界记:
same stack -> try...catch
promise chain -> .catch
await rejection -> try...catch around await
global fallback -> onerror / unhandledrejection
回答时先分同步和异步,再讲 Promise 与 async/await,最后补工程实践:预期错误给恢复路径,程序错误要上报,不要静默吞错。