同步/异步异常捕获
在 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')
}), // p1
wait(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 组件级别的,一个是全局的。