浅析async await错误处理问题及如何不使用try catch优雅地进行错误处理
一、Async 函数的错误处理
async 函数的语法不难,难在错误处理上。先来看下面的例子:
我们可以看到 Promise 报错后,a = await 1 并没有被执行。即当 async 函数中只要一个 await 出现 reject 状态,则后面的 await 都不会被执行。
解决办法是:可以添加 try catch。
// 正确的写法
let a;
async function correct() {
try {
await Promise.reject('error')
} catch (error) {
console.log(error);
}
a = await 1;
return a;
}
correct().then(v => console.log(a));
这样就会先打印 error,再打印 1。因此,如果有多个 await
则可以将其都放在 try/catch
中,但很显然,这样并不优雅。
// 我们需要对每一次异步操作进行错误处理
function async asyncTask(cb) {
try {
const asyncFuncARes = await asyncFuncA()
} catch(error) {
return new Error(error)
}
try {
const asyncFuncBRes = await asyncFuncB(asyncFuncARes)
} catch(error) {
return new Error(error)
}
try {
const asyncFuncCRes = await asyncFuncC(asyncFuncBRes)
} catch(error) {
return new Error(error)
}
}
二、npm包:await-to-js 及源码分析
作者是这样介绍这个库的:Async await wrapper for easy error handling without try-catch。中文翻译过来就是:无需 try-catch 即可轻松处理错误的异步等待包装器。
与上面对比,使用了await-to-js之后,我们可以这样的处理错误:
import to from './to.js';
function async asyncTask() {
const [err, asyncFuncARes] = await to(asyncFuncA())
if(err) throw new (error);
const [err, asyncFuncBRes] = await to(asyncFuncB(asyncFuncARes))
if(err) throw new (error);
const [err, asyncFuncCRes] = await to(asyncFuncC(asyncFuncBRes)
if(err) throw new (error);
}
简洁多了,看看源码,没写想到只有 15 行,完全都可以自己作为一个公共方法使用即可。
export function to<T, U = Error> (
promise: Promise<T>,
errorExt?: object
): Promise<[U, undefined] | [null, T]> {
return promise
.then<[null, T]>((data: T) => [null, data])
.catch<[U, undefined]>((err: U) => {
if (errorExt) {
const parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined];
}
return [err, undefined];
});
}
export default to;
上面是TS版的源码,但是考虑到有些同学可能还没接触过TS,我着重分析一下下面这版JS版的源码。
export function to(promise, errorExt) {
return promise
.then((data) => [null, data])
.catch((err) => {
if (errorExt) {
const parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined];
}
return [err, undefined];
});
}
export default to;
这里我们先抛开 errorExt 这个自定义的错误文本,可以看出:其代码的逻辑用中文解释是这样的:
- 无论成功还是失败,都返回一个数组,数组的第一项是和错误相关的,数组的第二项是和响结果相关的
- 成功的话,数组第一项也就是错误信息为空,数组第二项也就是响应结果正常返回
- 失败的话,数组第一项也就是错误信息为错误信息,数组第二项也就是响应结果返回undefined
经过上面的分析发现:其实挺简单的,但是,又确实挺巧妙的,我们也并不是做不到,而不是想不到。
这里我们再来看函数 to 的第二个参数 errorExt,不难发现这其实就是拿来用户自定义错误信息的,通过 Object.assign
将正常返回的 error 和用户自定义的,合并到一个对象里面供用户自己选择。