异步生成器:产生拒绝的承诺

CRi*_*ice 5 javascript generator promise async-await async-iterator

我一直在尝试使用异步生成器来尝试制作一个“承诺排序”生成器,该生成器接受一系列承诺并按照它们解决或拒绝的顺序逐个产生承诺。所以像这样:

async function* orderProms(prom_arr) {

    // Make a copy so the splices don't mess it up.
    const proms = [...prom_arr];

    while (proms.length) {
        // Tag each promise with it's index, so that we can remove it for the next loop.
        const {prom, index} = await Promise.race(proms.map((prom, index) => prom.then(
            () => ({prom, index}),
            () => ({prom, index})
        )));

        proms.splice(index, 1);
        yield prom;
    }
}
Run Code Online (Sandbox Code Playgroud)

想要像这样使用这个生成器:

const resAfter = (val, delay) => new Promise(res => setTimeout(() => res(val), delay));
const rejAfter = (val, delay) => new Promise((_, rej) => setTimeout(() => rej(val), delay));

const promises = [
    resAfter("Third", 3000),
    resAfter("First", 1000),
    rejAfter("Second", 2000), // NOTE: this one rejects!
];

(async () => {

    let ordered = orderProms(promises);

    let done = false;
    for (let next_promise = ordered.next(); !done; next_promise = ordered.next()) {
        const next = await next_promise
            .catch(err => ({done: false, value: `Caught error: ${err}`}));

        done = next.done;
        if (!done) console.log(next.value);
    }
})()
Run Code Online (Sandbox Code Playgroud)

但是,我注意到这将达到第二个承诺,然后生成器将停止。似乎是因为拒绝了“第二个”承诺。当被拒绝时,调用生成器将在生成器中yield prom创建异常。prom

但这就是我困惑的根源。我不想在这里创建异常,我只想产生被拒绝的承诺作为value迭代器结果。我不想把它拆开。这几乎就像被视为yield await prom;,但正如您所看到的,没有await调用。

这里发生了什么,我怎样才能简单地从这个生成器中按原样产生一个被拒绝的承诺。


这是上面的可运行代码片段中的代码:

async function* orderProms(prom_arr) {

    // Make a copy so the splices don't mess it up.
    const proms = [...prom_arr];

    while (proms.length) {
        // Tag each promise with it's index, so that we can remove it for the next loop.
        const {prom, index} = await Promise.race(proms.map((prom, index) => prom.then(
            () => ({prom, index}),
            () => ({prom, index})
        )));

        proms.splice(index, 1);
        yield prom;
    }
}

const resAfter = (val, delay) => new Promise(res => setTimeout(() => res(val), delay));
const rejAfter = (val, delay) => new Promise((_, rej) => setTimeout(() => rej(val), delay));

const promises = [
    resAfter("Third", 3000),
    resAfter("First", 1000),
    rejAfter("Second", 2000), // NOTE: this one rejects!
];

(async () => {

    let ordered = orderProms(promises);

    let done = false;
    for (let next_promise = ordered.next(); !done; next_promise = ordered.next()) {
        const next = await next_promise
            .catch(err => ({done: false, value: `Caught error: ${err}`}));

        done = next.done;
        if (!done) console.log(next.value);
    }
})()
Run Code Online (Sandbox Code Playgroud)

Ber*_*rgi 6

这几乎就像被视为一样yield await prom。这里发生了什么?

正是异步生成器的行为方式。

我怎样才能简单地从这个生成器中按原样产生一个被拒绝的承诺。

你不能。请注意,异步迭代器预计将被消耗

try {
    for await (const value of orderProms(promises)) {
        console.log(value);
    }
} catch(err) {
    console.error('Caught error: ', err);
}
Run Code Online (Sandbox Code Playgroud)

语法中不支持单独的错误处理。当出现异常时,循环停止,生成器完成。观点。

所以,你可以做什么?我看到三个选择:

  • 只需保持原样并将早期失败视为一项功能(类似于Promise.all
  • 处理错误(在orderProms将 Promise 传递给其中或之前)并生成 Promise 状态和值的元组

    for await (const value of orderProms(promises.map(prom =>
        prom.catch(err => `Caught error: ${err}`)
    ))) {
        console.log(value);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用普通(非async)生成器,您可以从中手动产生一个又一个承诺,以便能够按照您想要的方式使用它