为什么应该始终捕获 Promise 的错误?

huangapple go评论84阅读模式
英文:

Why should errors of a Promise be always caught?

问题

I am using @typescript-eslint/no-floating-promises rule in my project. This rule complains if I write code like this -

这个规则在我的项目中使用@typescript-eslint/no-floating-promises。如果我写像这样的代码,它会抱怨 -

    .then(retVal => doSomething(retVal));

It wants me to add a catch block for this Promise. It makes sense to me if I want some logic in my exception handling block. However, in many cases I don't need that. I am fine with the error being thrown. So, to suppress the error fired by this rule I just end up doing this -

如果我想在异常处理块中添加一些逻辑,那么它要求我添加一个catch块是有道理的。然而,在许多情况下,我不需要这样做。我可以接受错误被抛出。所以,为了抑制这个规则触发的错误,我最终只会做这个 -

	.then((retVal) => doSomething(retVal))
	.catch((error) => {
		throw error;
	});

Even if I don't add the catch block as above, the error still gets thrown the same way (at least as I see it in my console output). So, I don't get the point of explicitly specifying this catch block. Am I missing something? Is there really a difference in the way the error is thrown if I add/not add the catch block?

即使我不像上面那样添加catch块,error仍然以相同的方式被抛出(至少在控制台输出中是这样)。所以,我不明白明确指定这个catch块的意义在哪里。我是否遗漏了什么?如果我添加/不添加catch块,error的抛出方式是否真的有区别?

英文:

I am using @typescript-eslint/no-floating-promises rule in my project. This rule complains if I write code like this -

functionReturningPromise()
    .then(retVal => doSomething(retVal));

It wants me to add a catch block for this Promise. It makes sense to me if I want some logic in my exception handling block. However, in many cases I don't need that. I am fine with the error being thrown. So, to suppress the error fired by this rule I just end up doing this -

functionReturningPromise()
	.then((retVal) => doSomething(retVal))
	.catch((error) => {
		throw error;
	});

Even if I don't add the catch block as above, the error still gets thrown the same way (at least as I see it in my console output). So, I don't get the point of explicitly specifying this catch block. Am I missing something? Is there really a difference in the way the error is thrown if I add/not add the catch block?

答案1

得分: 2

以下是翻译好的内容:

评论者们都做得很好,回答了你的问题。但为了通过示例说明这为什么很重要,想象一下以下的代码:

Promise.reject();
setTimeout(() => console.log('hello'), 1000);

这段代码看起来相当无害 - 有一个未处理的空操作 promise 拒绝,而在启动后 1 秒钟后,程序将记录 'hello'

在浏览器中 - 这正是会发生的事情 - 浏览器会记录一个 "未捕获的 promise 拒绝" 错误,但除此之外不会理会它。

然而在 Node.js(截至 Node v15)中,未处理的 promise 拒绝是一个严重错误 - 这意味着当它检测到一个时,进程将退出!

你可以通过在终端中运行此代码来验证这一点(-e 意味着 "评估并运行此代码字符串"):

$ node -e "Promise.reject(); setTimeout(() => console.log('hello'), 1000)"
node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "undefined".] {
  code: 'ERR_UNHANDLED_REJECTION'
}

Node.js v18.12.1

你可以看到 'hello' 在 1 秒之前从未被打印出来,因为进程在之前终止了!

为了验证事情是否按预期工作,你可以将 .reject 更改为 .resolve

$ node -e "Promise.resolve(); setTimeout(() => console.log('hello'), 1000)"
hello

因此,如果你正在编写一个使用任何 LTS 支持的 Node.js 版本的应用程序,你绝对应该处理错误,否则你将面临意外崩溃的风险。

如果你的代码仅在浏览器中运行,那么你可能会想知道是否应该处理失败 - 毕竟,你的用户不会查看控制台,所以他们不会知道发生了什么不好的事情,那么谁会关心呢?从 "仅代码" 的角度来看,你是对的 - 但是当应用程序出现问题时,你的用户希望从他们的应用程序中得到反馈。

想象一下以下情景 - 你的 promise 跟踪着用户在你的应用程序中输入的一些数据的 API 调用结果。如果由于某种原因 API 调用失败,那么你的应用程序应该适当地响应并告诉用户发生了错误。

不处理它的替代方法可能意味着你的应用程序显示一个无限加载的旋转器,或者用户认为他们的数据已成功提交,而实际上并没有。无论哪种方式 - 这都是一种非常糟糕和有问题的用户体验!

最后,像 .catch(e => { throw e }) 这样的操作实际上并没有处理错误。当然,这段代码可以消除掉 linter 的警告 - 但你所做的只是创建一个新的拒绝 promise,该 promise 将被记录到控制台中。相反,你应该以某种方式将错误连接到你的应用程序的用户界面,例如简单的 .catch(e => { alert(e); throw e }) 甚至会更好。

英文:

The commenters have all done a good job of answering your question - but to show why this matters by example, imagine the following code:

Promise.reject();
setTimeout(() => console.log('hello'), 1000);

This code looks pretty innocuous - there is a no-op promise rejection which is unhandled and 1 second after startup the program will log 'hello'.

In a browser - this is exactly what will happen - the browser will log an "uncaught promise rejection" error but it will otherwise ignore it.

However in NodeJS (as of Node v15) unhandled promise rejections are a HARD ERROR - meaning that the process will exit when it detects one!

You can verify this by running the code in your terminal (-e means "evaluate and run this code string")

$ node -e "Promise.reject(); setTimeout(() => console.log('hello'), 1000)"
node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "undefined".] {
  code: 'ERR_UNHANDLED_REJECTION'
}

Node.js v18.12.1

You can see that the 'hello' never gets printed because the process terminates before 1s!

To validate that things work as expected, you can change the .reject to .resolve:

$ node -e "Promise.resolve(); setTimeout(() => console.log('hello'), 1000)"
hello

So if you're writing a NodeJS application with any LTS supported version you definitely should be handling the errors or else you risk unexpected crashes.

If your code only runs in browsers then you're probably wondering if you should handle the failure - after all your users aren't looking at the console so they won't know anything bad happened so who cares? And you'd be right from a "code only" point of view - however your users want feedback from their application when something goes wrong.

Imagine the following scenario - your promise tracks the result of an API call that submits some data the user entered in your application. If that API call fails for some reason, then your application should respond to that appropriately and tell the user something went wrong.

The alternative of handling it could mean your application shows an infinite loading spinner, or that a user thinks their data was submitted successfully when it actually wasn't. Either way - it's a very bad and broken UX!

Finally doing something like .catch(e => { throw e }) you're not actually handling the error. Sure this code silences the linter - but it all you're doing is creating a new, rejected promise which will get logged to the console. Instead you should wire the error into your application's UI in some way, eg something as simple as .catch(e => { alert(e); throw e }) would even be better.

huangapple
  • 本文由 发表于 2023年5月29日 21:11:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76357688.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定