稍后拒绝承诺

时间:2018-12-20 13:21:12

标签: javascript node.js promise async-await es6-promise

如何在以后检索诺言的结果?在测试中,我在发送进一步的请求之前正在检索电子邮件:

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

3 个答案:

答案 0 :(得分:6)

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);
    }
})();

答案 1 :(得分:2)

如果可以保证以后会处理promise拒绝,则可以将promise与伪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完成的变通方法。这种解决方法可行的唯一原因是只有两个例程,并且其中一个(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) {...}

这将产生清晰的控制流和冗长但更易于理解和测试的代码。

答案 2 :(得分:1)

所以,我想解释为什么在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

这可能就是我要做的。