node.js 应该 return new Error() 还是 throw new Error()?

个人偏向于 return new Error(), 对代码提示友好,如果用 TypeScript 不会忘记错误处理,但是这样导致代码和 golang …
关注者
168
被浏览
131,006

37 个回答

先说观点:抛出错误

首先要明确,既然你用了 TypeScript,不同类型的返回虽然可以做类型标注,但是这意味着这个函数的返回是不可靠的,需要动态对返回值是否为 Error 进行判断,如果别人来调用你的这个函数,他没办法通过 try catch 去捕获错误,而是通过判断返回值,如果这是一个比较基础的库函数,会直接打乱维护者对于代码的理解。一部分用 return,一部分又直接抛出错误。

接着思考,你能 return 出来的错误,一定是比较接近业务逻辑的错误,而代码本身的错误怎么办?接着你会发现,除了要处理 return 出来的错误,还得 catch 一下代码的运行时错误,这样的代码简直是灾难。

针对 throw new Error 无法检查错误类型,完全可以通过自定义 Error 来解决,不过多描述了,其他的回答有提到。

我们目前还采用了一种借鉴自 golang 模式的错误处理方式,就是其他回答提到的:

const [res, err] = await tryCatch(promise())

这样能保证不会因为 try catch 而创建一个无用的作用域,因为实践发现用了 try catch 很多人会写出这种代码:

let res
try {
  res = await promise()
} catch (e) {
  console.error(e)
}

// 用 res 做其他操作

try catch 创建了一个作用域,导致即使 res 不会再变化,他也得用 let 声明。

有的人说会把所有逻辑放到 try 里面,但是这样在 catch 到错误时你就没办法精确针对每一个 Error 进行错误提示了。

附一个简单的 tryCatch ts 实现:

export async function tryCatch <T, E = Error> (promise: Promise<T>): Promise<[T, E]> {
    try {
        const ret = await promise
        return [ret, null as E]
    } catch (e) {
        return [null as T, e]
    }
}

某大佬( @Dreamacro )也提给了 Deno 的代码优化:

对于任何有存在市场的编程风格和范式,我都不会明确反对,因为存在即是合理,只要它能或者曾经能带给你的项目一些收益。并且这种风格在项目中是统一的,那就放心去做。

不过言归正传,return一个error,在js里,我个人感觉是一种非主流的存在。

这就像曾经node原始社会中用烂了的回调一样,第一个参数永远是err。而你要在无尽的嵌套中去检查它。这在promise和async await语法糖已经普及的今天来看,无疑是发展的倒退。你也提到了你甚至已经在用typescript了,那我就更不能理解为什么要return error。难道你每个函数的返回类型都拓展预留一个error字段?

另外把error返回给调用者而不是throw,这已经失去了定位error发生地点的精确位置。我不明白这样怎么会提高可读性?

golang里面的result, err := func() 自成一套体系,一方面是它的错误处理机制在设计的时候本来和js甚至java C#就有很大区别。另外golang可以方便地返回多个结果,附带一个err即可。而ts里面光定义类型就够绕的了。

return而不是throw还有个问题就是你要依赖调用者的严谨处理来保证程序不会忽略这个错误。因为error也是一个结果,不会自动抛出,如果return以后没做好处理,那这个很有可能就违背了开发人员的原始意图,然后就产生了bug。而throw则确保了执行的终止。