await的错误处理问题,一个issue引发的ts社区的讨论
前提
大家都知道try-catch对异步流程中的错误有时候是捕捉不到的,eg:
function func1() {
try {
func2();
} catch (error) {
console.log('error');
}
}
function func2() {
setTimeout(() => {
throw new Error('error')
}, 1000)
}
func1();
执行这些代码,你会发现过了一秒后程序直接报错,console.log('error')并没有执行,也就是func1并没有捕捉到func2的异常。这就是异步的问题所在。
我们一般的做法就是直接加上async/await:
async function func1() {
try {
await func2();
} catch (error) {
console.log('error');
}
}
function func2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject()
}, 1000)
})
}
func1();
func2被Promise封装,reject后会被func1的try-catch捕获到,像现在的axios很多库都已经封装好Promise方便直接await。
问题
这里我们要讨论的就是对await到底用try-catch,if(err)的方式。
if(err)的方式在node中的很多异步回调函数中很多场景都用到,err永远是node结果回调函数的第一个参数,但此情景又与node中的不一样。对await的错误处理要想获取到err,需要额外增加一个函数,用try-catch捕获到的err return出去。
eg:
每次使用都要引用errCatch()。
async function errCatch(asyncFunc) {
try {
let res = await asyncFunc();
retrun [null, res]
} catch (error) {
return [error,null];
}
}
这样func1就要写成:
async function func1() {
let [err, res] = errCatch(asyncFunc);
if (err) {
//.....
}
}
那么这种写法比直接try-catch有什么好处,第一点就是更加语义化了,这个问题在deno社区就这个问题的ts写法引起过讨论,可以看看这个issue:
https://github.com/denoland/deno_std/issues/525
第一个意思是 Golang 只需要处理 return 出来的错误,很少有运行时错误,即便有,也是可以在 defer 统一处理。 第二个意思是,像一个读流 copy 到另一个写流。假如因为某个异常中断的话,我们需要知道是读取到了第 n 的细节中断的,然后错误是什么。这样的场景就适合这样使用。(具体可以再copy中实现)
(但我觉得这个其实是特殊场景,用try-catch也能控制)
其实一个老哥也指出传统的promise做法,因为await的错误不捕获会导致整个async函数的中断,用catch也能避免中断并捕捉到错误。
开issue的作者是说使用这种style的原因是这样的:
最后维护者是这么说的:
大概意思就是说
“我们可以在utils/async.ts中实现这一点,供我们自己使用,并作为用户的一个选项提供。但正如我所说,在更多的情况下,Try-Catch是进入JS的正确方法,或者提供像上面提到的@J-F1这样的catch处理程序。
(作为一个夸张的例子,C中的全局errno在某些情况下可能更干净,但是我们在JS中不做这样的构造,因为它违背了预期的语言用法。
如果您认为这些助手非常有用,那么如果您能够贡献并提交一个pr来将它们添加到utils/async.ts中,这将是非常好的,并且有可能在将来说服更多的JS开发人员尝试这样的处理方式,这样有一天它就足够惯用了。
(我目前受到一些法律纠纷的约束,因此不幸的是,我自己做不到。)”
其实上面issue作者举的那个例子就是说在官方库中有些错误不用被处理,只是做检测的情况下,不用写try-catch,catch的e就更不用写上了,用了helper的style后,可以直接这样写:
const statInfo = await maybe(stat(fn)); // yields FileInfo | null
const mode = statInfo ? statInfo.mode : null;
看上去更加舒服???
知乎上关于node.js 应该 return new Error() 还是 throw new Error()的讨论
后面突然想到了之前看到的一个话题的讨论,其实和这个很像,某种程度甚至是一样的了:
node.js 应该 return new Error() 还是 throw new Error()?
参考:
https://www.zhihu.com/question/323618147
https://juejin.im/post/5d25b39bf265da1bb67a4176
https://github.com/denoland/deno_std/issues/525
http://es6.ruanyifeng.com/#docs/async