1. try/catch
es6 的初学者必须知道的捕获错误的方法,因为它是相对来说最保险的,既可以捕获同步错误也可以捕获异步错误。
捕获异步错误:
run();
async function run() {
try {
await Promise.reject(new Error('Oops!'));
} catch (error) {
error.message; // "Oops!"
}
}
捕获同步错误:
run();
async function run() {
const v = null;
try {
await Promise.resolve('foo');
v.thisWillThrow;
} catch (error) {
// "TypeError: Cannot read property 'thisWillThrow' of null"
error.message;
}
}
新手用 async/await
容易犯的错误之一,是忘记捕获错误,这是大忌,它会导致出现“S级”的异常:**控制台既没有报错,期待的效果也没有work,会导致很难定位问题。**以下的示例取自于一个个人比较常见的情景:
import api from './api.js' // 引入了一个请求库
run();
async function run() {
const res = await axios.get('xxxx')
// 期待打印出请求结果,但因为没有做错误处理,
// 导致当网络请求出错时,这一行既没有走到,
// 控制台也没有抛出 Error,会很难定位问题
console.log(res)
}
但这不意味着 try/catch
就万无一失了。try
代码块中被 return
的 rejected 的 promise 是无法被捕获到的:
run();
async function run() {
try {
// 注意: 这是一个 `return` ,不是 `await`
return Promise.reject(new Error('Oops!'));
} catch (error) {
// 不会运行
}
}
其实,将 return
改成 return await
可以解决这个问题的,但容易遗漏。
2. 以Golang 风格捕获错误
用 .then()
将 rejected 状态的 promise 转成成功状态的 promise 并返回错误,可以用 if (err)
来检查是否出错
run();
async function throwAnError() {
throw new Error('Oops!');
}
async function noError() {
return 42;
}
async function run() {
// `.then(() => null, err => err)` 模式下,
// 当错误发生时返回一个 error ,否则返回 null
let err = await throwAnError().then(() => null, err => err);
if (err != null) {
err.message; // 'Oops'
}
err = await noError().then(() => null, err => err);
err; // null
}
如果同时需要成功的值和错误信息,可以像写 Golang
一样写 Javascript
。
run();
async function throwAnError() {
throw new Error('Oops!');
}
async function noError() {
return 42;
}
async function run() {
// `.then(v => [null, v], err => [err, null])` 的模式
// 让你能用数组的形式同时获取 error 和 result
let [err, res] = await throwAnError().
then(v => [null, v], err => [err, null]);
if (err != null) {
err.message; // 'Oops'
}
[err, res] = await noError().
then(v => [null, v], err => [err, null]);
err; // null
res; // 42
}
如果喜欢这种写法,还可以对它做一个封装:
// to.js
export default function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
// use
import to from './to.js';
run();
async function throwAnError() {
throw new Error('Oops!');
}
async function noError() {
return 42;
}
async function run() {
// 用 to 来曝光promise
let [err, res] = await to(throwAnError())
if (err != null) {
err.message; // 'Oops'
}
[err, res] = await to(noError())
err; // null
res; // 42
}
如果需要拓展 error 的信息、是否 errorFirst,甚至可以做更复杂的封装:
// to.js
function to (promise, errorProps = {}, errorFirst = true) {
return promise.then((data) => errorFirst ? [null, data] : [data, null])
.catch(err => {
if(errorProps) Object.assign(err, errorProps)
errorFirst ? [err, null] : [null, err]
})
}
3. catch()
try/catch 和 Golang风格的错误处理都有它们的用途,但是,要确保你处理了 run()
函数里所有错误的、最好的方法,是用 run().catch()
(前提:run
是异步函数)。换句话说,在调用函数时就处理错误,而不是单独处理每个错误。
run().
catch(function handleError(err) {
err.message; // Oops!
}).
// 处理 `handleError()`中的任何错误:如果出错则杀死进程
catch(err => { process.nextTick(() => { throw err; }) });
async function run() {
await Promise.reject(new Error('Oops!'));
}
记住,所有的 async 函数总是返回 promises。如果函数内有任何未捕获的错误发生了,这个 promise 也会 rejected。如果你的 async 函数返回了一个 rejected 的 promise,返回的 promise 也会 rejected 。
run().
catch(function handleError(err) {
err.message; // Oops!
}).
// 处理 `handleError()`中的任何错误:如果出错则杀死进程
catch(err => { process.nextTick(() => { throw err; }) });
async function run() {
// 注意:这是一个 `return` ,不是 `await`
return Promise.reject(new Error('Oops!'));
}
总结
try/catch | Golang风格 | .catch() | |
---|---|---|---|
优点 | - 最保险的,可捕获同步、await的异步错误 | - 流程控制准确 - 优雅 | - 能捕获异步函数体内的同步和抛出的异步错误 |
缺点 | - 无法捕获 try 块中,被return 的异步错误(可用return await 解决)- 不能优雅地对错误分别处理 - 不适用于流程中不需要中断的错误 - 无法避免catch本身抛出异常且不好处理 | - 大量重复 if(err !== null) ,可能遗漏- 无法捕获外部函数体内的同步错误 | - 只适用于有 .catch() 方法的异步函数 |
场景 | 非常适合简单流程、不希望出错后影响后续执行流程的情景 | 适合链路长、较复杂的异步流程;适合于单个环节出错不影响流程 | 适合处理期望外的错误,及时处理;适合给.catch() 做兜底 |
参考资料
http://thecodebarbarian.com/async-await-error-handling-in-javascript.html
https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/
https://github.com/huruji/blog/issues/61