稍后赶上承诺拒绝[重复]
Posted
技术标签:
【中文标题】稍后赶上承诺拒绝[重复]【英文标题】:Catch Promise rejection at a later time [duplicate] 【发布时间】:2019-05-21 00:42:07 【问题描述】:我如何在以后检索承诺的结果?在测试中,我在发送更多请求之前检索了一封电子邮件:
const email = await get_email();
assert.equal(email.subject, 'foobar');
await send_request1();
await send_request2();
在缓慢的电子邮件检索过程中如何发送请求?
起初,我考虑过等一下邮件:
// This code is wrong - do not copy!
const email_promise = get_email();
await send_request1();
await send_request2();
const email = await email_promise;
assert.equal(email.subject, 'foobar');
如果get_email()
成功,则此方法有效,但如果get_email()
在相应的await
之前失败,则此方法失败,并带有完全合理的UnhandledPromiseRejectionWarning
。
当然,我可以使用Promise.all
,像这样:
await Promise.all([
async () =>
const email = await get_email();
assert.equal(email.subject, 'foobar');
,
async () =>
await send_request1();
await send_request2();
,
]);
但是,它使代码更难阅读(它看起来更像基于回调的编程),特别是如果以后的请求实际上依赖于电子邮件,或者存在一些嵌套。是否可以在以后存储承诺的结果/异常和await
?
如果需要,here is a testcase 带有有时会失败但有时会工作的模拟,时间随机。它绝不能输出UnhandledPromiseRejectionWarning
。
【问题讨论】:
我不明白这个。使用 async/await 时,您可以使用 try/catch 捕获错误。您可以使用 .catch() 链接到承诺对象。您发布的缓冲区解决方案基本上只是 .then() 的实现。因此,只要您的承诺被拒绝,稍后添加 .catch() 就会给您错误。 @Shilly 太好了,介意发布您的解决方案吗?我在下面的答案包含一个完整的测试(有时会失败,有时会工作,时间不同)。在节点中多次运行时,此缓冲区答案总是输出正确的结果,并且 never 会导致UnhandledPromiseRejectionWarning
。如果我只需要一对await
s 或then
s,那就更好了!
什么意思?你需要捕捉个别错误吗?是不是你想要一个 UnhandledPromiseRejectionWarning 而不是抓住它?
不,我确实不想要任何UnhandledPromiseRejectionWarning
s。任何错误都应该像往常一样在 Promise 中抛出。换句话说,示例代码应该始终工作或捕获异常(输出main error: Error: failure
)。
真的有必要用同样的方法检索邮件和发送请求吗?如果可能的话,分派三个单独的线程似乎会更干净。您在这里有效地尝试使用不适合这项工作的工具来解决并发问题。
【参考方案1】:
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const send_request1 = () => wait(300), send_request2 = () => wait(200);
async function get_email()
await wait(Math.random() * 1000);
if (Math.random() > 0.5) throw new Error('failure');
return subject: 'foobar';
const assert = require('assert');
async function main()
// catch possible error
const email_promise = get_email().catch(e => e);
await send_request1();
await send_request2();
// wait for result
const email = await email_promise;
// rethrow eventual error or do whatever you want with it
if(email instanceof Error)
throw email;
assert.equal(email.subject, 'foobar');
;
(async () =>
try
await main();
catch(e)
console.log('main error: ' + e.stack);
)();
【讨论】:
只需将顶行更改为const email_promise = get_email(); email_promise.catch(e => e)
,不再需要所有重新抛出的代码,是吗?
重新抛出代码是必要的,没有它会'resolve'并出现错误,例如'resolve(new Error ())'【参考方案2】:
如果保证以后会处理 Promise 拒绝,则可以将 Promise 与 dummy catch
链接以抑制检测未处理的拒绝:
try
const email_promise = get_email();
email_promise.catch(() => ); // a hack
await send_request1();
await send_request2();
const email = await email_promise;
assert.equal(email.subject, 'foobar');
catch (err) ...
这种方法的问题是有两个并发例程,但代码没有表达这一点,这是通常使用Promise.all
完成的解决方法。这种解决方法可行的唯一原因是只有 2 个例程,其中一个 (get_email
) 只需要与 then
/await
链接一次,所以它的一部分 (assert
)可以延期。如果有 3 个或更多例程,或者例程涉及多个then
/await
,问题会更加明显。
如果 Promise.all
引入了不需要的 lambda 嵌套级别,可以通过将例程编写为命名函数来避免这种情况,即使它们没有在其他任何地方重用:
async function assertEmail()
const email = await get_email();
assert.equal(email.subject, 'foobar');
async function sendRequests()
await send_request1();
await send_request2();
...
try
await Promise.all([assertEmail(), sendRequests()]);
catch (err) ...
这会产生清晰的控制流和冗长但更易于理解和可测试的代码。
【讨论】:
你能详细说明 3 个或更多 Promise 的问题吗?因为那是我的代码中实际拥有的。我的真实代码中的问题是以后的请求可能取决于电子邮件。此外,它作为一系列await
语句更具可读性,特别是如果它只允许我们原生表达同步点(= 所有 3 个并发任务必须在此处完成),没有嵌套或辅助函数.
它作为一系列等待语句更具可读性 - 这里让我感到困惑的是你任意选择了一个例程(一系列 send_request*)来编写它以类似同步的方式,而另一个例程(get_email)横向运行。如果有更多的承诺,控制流程将更难遵循。为什么这些例程中的一个应该被认为是一流的并且是连续编写的,而其他例程却是横向的?这在这里看起来很可疑,因为通常这样的例程被视为平等。
get_email
是一个缓慢的操作,不需要进一步的监督,所以是的,它对主要流程并不重要,至少在我真正需要它的返回值之前。这对于并发应用程序来说真的那么不寻常吗?【参考方案3】:
所以,我想解释一下为什么我们在 Node.js 中会这样:
// Your "incorrect code" from before
const email_promise = get_email(); // we acquire the promise here
await send_request1(); // if this throws - we're left with a mess
await send_request2(); // if this throws - we're left with a mess
const email = await email_promise;
assert.equal(email.subject, 'foobar');
也就是说,我们这样做的原因是不处理“多次拒绝并且可能没有清理”的情况。我不确定你是如何得到Promise.all
的长代码的,但是这个:
await Promise.all([
async () =>
const email = await get_email();
assert.equal(email.subject, 'foobar');
,
async () =>
await send_request1();
await send_request2();
,
]);
其实可以这样:
let [email, requestData] = await Promise.all([
get_email(),
send_request1().then(send_request2)
]);
// do whatever with email here
这可能是我会做的。
【讨论】:
嗯,这就是为 *** 简化代码的缺点:我的真实代码要复杂得多(这是一个集成测试)。发送了十几个请求,使用同步代码来计算值——这只是现实生活中的代码。用一个简单的async..await
表达一些请求的异步性将是awesome,并且具有更多的可读性然后有一堆嵌套的Promise.all
。以上是关于稍后赶上承诺拒绝[重复]的主要内容,如果未能解决你的问题,请参考以下文章