如何映射异步生成器?

5

假设我们有一个异步生成器:

exports.asyncGen = async function* (items) {
  for (const item of items) {
    const result = await someAsyncFunc(item)
    yield result;
  }
}

这个生成器能否进行映射?本质上,我想做到这一点:

const { asyncGen } = require('./asyncGen.js')

exports.process = async function (items) {
  return asyncGen(items).map(item => {
    //... do something
  })
}

目前,.map 无法识别异步迭代器。

替代方案是使用 for await ... of 但这远不如使用 .map 简洁优雅。


1
.map()只存在于数组上,而不是生成器上 - 无论是异步还是非异步。 - VLAZ
3个回答

8
迭代器方法提案提供了这个方法,但目前仅处于第二阶段。您可以使用一些polyfill,或编写自己的map帮助函数。
async function* map(asyncIterable, callback) {
    let i = 0;
    for await (const val of asyncIterable)
        yield callback(val, i++);
}

exports.process = function(items) {
    return map(asyncGen(items), item => {
       //... do something
    });
};

2

TL;DR - 如果映射函数是异步的:

为了使asyncIter在生成下一个值之前不等待每个映射,可以执行以下操作:

async function asyncIterMap(asyncIter, asyncFunc) {
    const promises = [];
    for await (const value of asyncIter) {
        promises.push(asyncFunc(value))
    }
    return await Promise.all(promises)
}

// example - how to use:
const results = await asyncIterMap(myAsyncIter(), async (str) => {
    await sleep(3000)
    return str.toUpperCase()
});

更多演示:

// dummy asyncIter for demonstration

const sleep = (ms) => new Promise(res => setTimeout(res, ms))

async function* myAsyncIter() {
    await sleep(1000)
    yield 'first thing'
    await sleep(1000)
    yield 'second thing'
    await sleep(1000)
    yield 'third thing'
}

那么

// THIS IS BAD! our asyncIter waits for each mapping.

for await (const thing of myAsyncIter()) {
    console.log('starting with', thing)
    await sleep(3000)
    console.log('finished with', thing)
}

// total run time: ~12 seconds

更好的版本:

// this is better.

const promises = [];

for await (const thing of myAsyncIter()) {
    const task = async () => {
        console.log('starting with', thing)
        await sleep(3000)
        console.log('finished with', thing)
    };
    promises.push(task())
}

await Promise.all(promises)

// total run time: ~6 seconds

2
替代方案是使用for await ... of,但这并不像.map那样优雅。
为了实现一个优雅而高效的解决方案,这里提供了一个使用iter-ops库的例子:
import {pipe, map} from 'iter-ops';

const i = pipe(
    asyncGen(), // your async generator result
    map(value => /*map logic*/)
); //=> AsyncIterable
  • 它很优雅,因为其语法简洁清晰,适用于任何可迭代对象或迭代器,而不仅仅是异步生成器。
  • 它更加灵活和可重用,因为您可以将许多其他运算符添加到同一个管道中。

由于它生成标准的JavaScript AsyncIterable,所以您可以执行以下操作:

for await(const a of i) {
    console.log(a); //=> print values
}

附言:我是iter-ops的作者。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接