前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库async/await 错误捕获边界
JavaScript异步与 Promise

async/await 里哪些错误该用 try/catch 接,哪些不该接?

能预料、能处理的错误用 try/catch 接住,比如接口返回失败、用户没权限,接住后给提示或走降级。代码 bug 这类不知道怎么恢复的错误不要接,让它抛到全局的 unhandledrejection 和监控里去。另外 try/catch 只管被 await 的那个 Promise。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#异步与 Promise#异步编程
先看核心答案读代码示例
理解线索

一个错误该在哪一层被接住

  1. 业务错误接口失败、参数不合法、没权限,在调用处 catch 后提示或降级
  2. 程序 bugTypeError、读 undefined 的属性,不要吞掉,让它上报
  3. 全局兜底unhandledrejection 事件和 Sentry 这类监控,收集漏网的拒绝

catch 里只写一句 console.log(err) 等于把错误吞了,调用方会以为成功,拿到的返回值是 undefined。

核心回答

先记住这个答案

try/catch 能接住两类错误:try 块里同步抛出的错误,以及被 await 的 Promise 变成 rejected 时抛出的错误。没有被 await 的 Promise、setTimeout 回调里抛的错,它都接不住。至于该不该接,看你接住后能不能做点有意义的事:接口失败能提示用户、能重试、能降级,就在调用处接;代码 bug 接住了也修不好,应该让它继续往上抛,由全局的 unhandledrejection 和监控系统收集。

  • try/catch 只能接住被 await 的 Promise 拒绝
  • 漏写 await、定时器回调里的错误都接不住
  • 能给用户交代的错误才就地接,bug 交给全局兜底
  • try 里要写 return await,否则拒绝会绕过 catch

await 让拒绝变成了普通的 throw

await 一个 rejected 的 Promise 时,那一行会像 throw 一样抛出拒绝原因。所以异步错误也能用同步的写法接住。

同一个函数,成功和失败各走一次JavaScript
function loadUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => (id > 0 ? resolve({ id }) : reject(new Error('用户不存在'))), 10)
  })
}

async function show(id) {
  try {
    const user = await loadUser(id) // 拒绝时,这一行直接抛错
    console.log('拿到用户', user.id)
  } catch (err) {
    console.log('提示用户:' + err.message) // 业务错误,就地给提示
  }
}

show(1)
show(0)
查看输出与解释
拿到用户 1
提示用户:用户不存在

show(0) 里 loadUser 被拒绝,await 那一行抛出错误,后面的 console.log 不再执行,直接跳进 catch。这里接住是合理的:用户不存在是能预料的情况,给个提示就是完整的处理。

try/catch 接不住的几种写法

判断标准只有一个:错误发生的那一刻,代码是不是还停在这个 try 块里。只要 try 块已经执行完了,后面才出的错就和它没关系。

漏写 await 和回调里抛错JavaScript
async function save() {
  try {
    loadUser(0) // 没写 await,拒绝发生时 try 早就执行完了
    setTimeout(() => {
      JSON.parse('{') // 这里抛的错也到不了下面的 catch
    }, 0)
    console.log('try 正常结束')
  } catch (err) {
    console.log('永远不会走到这里')
  }
}
// 结果:打印「try 正常结束」,随后控制台出现未处理的拒绝和一个未捕获的错误

loadUser(0) 没有 await,它的拒绝会变成「未处理的拒绝」,浏览器触发全局的 unhandledrejection 事件。setTimeout 的回调是在另一轮事件循环里执行的,那时 save 早就返回了,要接只能在回调内部自己写 try/catch。

return 和 return await 在 try 里不一样
在 try 块里直接 return loadUser(0),函数把这个 Promise 原样交出去,拒绝发生在函数外面,本地的 catch 不会执行。写成 return await loadUser(0),拒绝在 try 里就抛出来了,catch 才能生效。不在 try 块里时,两种写法效果一样,await 可以省。

哪些错误不该在业务代码里接

TypeError: Cannot read properties of undefined 这类错误说明代码写错了。在业务函数里把它接住,只打一行日志然后继续跑,界面会停在一个半成功的状态,问题还被藏了起来。

比较稳的分层是这样:请求层统一处理网络错误和 401 这类通用状态,比如跳登录;页面里只接和当前操作有关的业务错误,比如「库存不足」弹个提示;剩下的全部往上抛,由 window.addEventListener('unhandledrejection', ...) 和 Sentry 统一上报。React 里渲染阶段的错误交给错误边界组件,但错误边界接不住事件处理和异步代码里的错误,这部分还是要靠 try/catch 或全局事件。

回答前,多想一步

容易答错的地方

每个 await 都包一层 try/catch
包得越多,越容易在某一层把错误吞掉。只在能做出处理的那一层接,其他层让错误自然冒泡。一个请求失败要提示,一个请求失败要回滚表单,这两种才值得分别接。
catch 里打完日志就结束
catch 之后函数正常返回 undefined,调用方会以为操作成功了。处理不了就在 catch 里重新 throw err,或者返回一个明确的失败结果,比如 { ok: false, error }。
以为 try/catch 能接住所有异步错误
它只管被 await 的那一个 Promise。比如先同时发出两个请求 const a = loadA() 和 const b = loadB(),再先 await a 然后 await b。如果 b 在 a 完成前就被拒绝,这时还没有人 await 它,浏览器会先报一次未处理的拒绝。并发请求要写成 await Promise.all([loadA(), loadB()]),让一个 await 同时盯住两个。
试着用自己的话回答

面试官还会怎么问?

不想写 try/catch,还有别的写法吗?

可以直接在调用处接 .catch(),比如 const user = await loadUser(id).catch(() => null),失败时拿到 null 走降级。也有人封装成 [err, data] 的元组返回,类似 Go 的风格,适合一个函数里有多个互不相关的请求、各自要单独处理失败的场景。

finally 里适合写什么?

写不管成功失败都要做的收尾,比如关掉 loading、释放锁。注意别在 finally 里 return,它会覆盖 try 或 catch 里的返回值,连抛出的错误也会被吞掉。

全局 unhandledrejection 能代替 try/catch 吗?

不能。它只能在最后收集和上报,这时已经不知道是哪个按钮、哪个操作出的错,没法给用户具体提示,也没法回滚状态。它的定位是兜底和监控,业务错误还是要在调用处处理。

从一道题,走向一组知识

把知识连起来

异步与 Promise

并发池里任务谁先完成不一定,怎么让结果还按原来的顺序排?

并发池里某个任务失败时,是就地记下错误还是整体中断,正是这篇讲的「在哪一层接错误」。

异步与 Promise

手写一个限制最大并发数的异步任务调度器,怎么实现?

调度器里每个任务的拒绝都要转交给调用方,写不好就会出现接不住的错误。

参考资料

  • JavaScript:异步与 Promise参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. await 让拒绝变成了普通的 throw
  3. try/catch 接不住的几种写法
  4. 哪些错误不该在业务代码里接
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑