同步/异步异常捕获
在 JS 中,我们通常使用 try ... catch 来捕获异常。针对于捕获同步异常是比较容易的:
try {throw new Error('err')} catch (e) {console.log(e) // caught}
但对于异步错误,捕获起来需要做额外的操作:
try {;(async () => {throw new Error('err') // uncaught})()} catch (e) {console.log(e)}
异常无法捕获的原因是异步代码并不在 try catch 上下文中执行。要捕获 async 函数中的异常,可以调用 .catch,因为 async 函数返回一个 Promise
;(async () => {throw new Error('err')}).catch((e) => console.log(e)) // caught
也可以在函数体内直接使用 try catch:
;(async () => {try {throw new Error('err')} catch (e) {console.log(e) // caught}})()
类似的,如果在函数体内部捕获异常,则要使用 Promise.all:
;(async () => {try {await Promise.all([1, 2, 3].map(async () => {throw new Error('err')}))} catch (e) {console.log(e) // caught}})()
这里使用 await 等待异常返回,所以可以进行捕获。
若是 Promise 内再包含一个异步:
new Promise(() => {setTimeout(() => {throw new Error('err') // uncaught}, 0)}).catch((e) => {console.log(e)})// 若想捕获,需要使用 reject 的方式进行抛出new Promise((resolve, reject) => {setTimeout(() => {reject('err') // caught}, 0)}).catch((e) => {console.log(e)})
并行的 Promise 建议使用 Promise.all 来进行处理:
await Promise.all([wait(1000).then(() => {throw new Error('err')}), // p1wait(2000),])
Promise 的错误会随着 Promise 进行链式传递,因此建议把 Promise 内多次的异步行为改写为多条链的方式,在最后使用 catch 来进行捕获。
new Promise((res, rej) => {setTimeout(() => {throw Error('err')}, 1000) // 1}).catch((error) => {console.log(error)})// 将上面的例子改写为new Promise((res, rej) => {setTimeout(res, 1000) // 1}).then((res, rej) => {throw Error('err')}).catch((error) => {console.log(error)})
在 async 函数中直接抛出异常是无法被 try catch 捕获的
try {(async () => {throw new Error('err')})()} catch (err) {console.log(err)}
此时我们需要将异常的抛出方式改为 Promise.reject。
(async () => {try {await (async () => {return Promise.reject(new Error('err'))})()} catch (err) {console.log(err)}})()
在 async 函数中
async () => {return 1}// 等同于async () => {return Promise.resolve(1)}async () => {await 1// 或者单独的 1}// 等同于async () => {return Promise.resolve(1).then(() => undefined)}
全局捕获
仅通过 try catch 和 then 捕获同步、异步错误是不够的,这些都是局部的捕获手段,当我们无法保证所有代码都处理了异常的时候,需要进行全局异常监控,通常有以下两种方式:
- window.addEventListener(‘error’)
- widnow.addEventListener(‘unhandledrejection’)
error 可以监听所有同步、异步的运行时错误,但无法监听语法、接口、资源加载错误。而 unhandledrejection 可以监听到 Promise 中抛出的,未被 .catch 捕获的错误。
在具体的前端框架中,也可以通过框架提供的错误监听方案解决部分问题,比如 React 的 Error Boundaries、Vue 的 error handler,一个是 UI 组件级别的,一个是全局的。
