node.js - 可以让事件处理程序等到异步/基于 Promise 的代码完成吗?

标签 node.js promise es6-promise node-streams backpressure

我在 nodejs 模式下使用出色的 Papa Parse 库,将超过 100 万行的大型(500 MB)CSV 文件流式传输到一个缓慢的持久性 API 中,该 API 一次只能接受一个请求。持久性 API 基于 Promise s,但是从 Papa Parse,我在 中收到每个解析的 CSV 行同步 事件如下:parseStream.on("data", row => { ... }我面临的挑战是 Papa Parse 从流中转储其 CSV 行如此之快,以至于我的缓慢持久性 API 无法跟上。因为爸爸是 同步 我的 API 是 promise -基于,我不能只是打电话await doDirtyWork(row)on事件处理程序,因为同步和异步代码不混合。
或者他们可以混合,我只是不知道如何?
我的问题是,我可以让爸爸的事件处理程序等待我的 API 调用完成吗?直接在 on("data") 中执行持久化 API 请求的方式事件,使on()在肮脏的 API 工作完成之前,函数会以某种方式徘徊吗?
就内存占用而言,我到目前为止的解决方案并不比使用 Papa 的非流模式好多少。我实际上需要排队 on("data")的洪流事件,以生成器函数迭代的形式。我也可以在一个数组中排队 promise 工厂并在循环中处理它。无论哪种方式,我最终都会将几乎整个 CSV 文件保存为内存中 future promise ( promise 工厂)的巨大集合,直到我缓慢的 API 调用一直工作。

async importCSV(filePath) {
    let parsedNum = 0, processedNum = 0;

    async function* gen() {
        let pf = yield;
        do {
            pf = yield await pf();
        } while (typeof pf === "function");
    };

    var g = gen();
    g.next();


    await new Promise((resolve, reject) => {
        try {
            const dataStream = fs.createReadStream(filePath);
            const parseStream = Papa.parse(Papa.NODE_STREAM_INPUT, {delimiter: ",", header: false});
            dataStream.pipe(parseStream);

            parseStream.on("data", row => {

                // Received a CSV row from Papa.parse()

                try {
                    console.log("PA#", parsedNum, ": parsed", row.filter((e, i) => i <= 2 ? e : undefined)
                    );
                    parsedNum++;

                    // Simulate some really slow async/await dirty work here, for example 
                    // send requests to a one-at-a-time persistence API

                    g.next(() => {  // don't execute now, call in sequence via the generator above
                        return new Promise((res, rej) => {
                            console.log(
                                "DW#", processedNum, ": dirty work START",
                                row.filter((e, i) => i <= 2 ? e : undefined)
                            );
                            setTimeout(() => {
                                console.log(
                                    "DW#", processedNum, ": dirty work STOP ",
                                    row.filter((e, i) => i <= 2 ? e : undefined)
                                );
                                processedNum++;
                                res();
                            }, 1000)
                        })
                    
                    });
                } catch (err) {
                    console.log(err.stack);
                    reject(err);                    
                }
            });
            parseStream.on("finish", () => {
                console.log(`Parsed ${parsedNum} rows`);
                resolve();
            });

        } catch (err) {
            console.log(err.stack);
            reject(err);                    
        }
    });
    while(!(await g.next()).done);
}
那么爸爸为什么要着急呢?为什么不让我把文件处理得慢一点——原始 CSV 文件中的数据不会跑掉,我们有几个小时来完成流式传输,为什么用 on("data") 锤我 |我似乎无法放慢速度的事件?
所以我真的需要让爸爸变得更像爷爷,并最小化或消除 CSV 行的任何排队或缓冲。理想情况下,我能够将 Papa 的解析事件与我的 API 的速度(或缺乏速度)完全同步。因此,如果不是因为异步代码不能使同步代码“休眠”的教条,我最好将每个 CSV 行发送到 API 爸爸事件内 ,而且只有 然后 将控制权交还给爸爸。
建议?事件处理程序与我的异步 API 的缓慢之间的某种“松散耦合”也很好。我不介意是否有几百行排队。但是当数以万计堆积时,我会很快用完堆。

最佳答案

Why hammer me with on("data") events that I can't seem to slow down?


你可以,你只是没有要求爸爸停下来。您可以通过拨打 stream.pause() 来做到这一点。 ,然后是 stream.resume() 利用 Node 流的内置 背压 .
然而,有一个比在基于回调的代码中自己处理这个更好的 API:use the stream as an async iterator !当您awaitfor await 的 body 里循环,生成器也必须暂停。所以你可以写
async importCSV(filePath) {
    let parsedNum = 0;

    const dataStream = fs.createReadStream(filePath);
    const parseStream = Papa.parse(Papa.NODE_STREAM_INPUT, {delimiter: ",", header: false});
    dataStream.pipe(parseStream);

    for await (const row of parseStream) {
        // Received a CSV row from Papa.parse()
        const data = row.filter((e, i) => i <= 2 ? e : undefined);
        console.log("PA#", parsedNum, ": parsed", data);
        parsedNum++;
        await dirtyWork(data);
    }
    console.log(`Parsed ${parsedNum} rows`);
}

importCSV('sample.csv').catch(console.error);

let processedNum = 0;
function dirtyWork(data) {
    // Simulate some really slow async/await dirty work here,
    // for example send requests to a one-at-a-time persistence API
    return new Promise((res, rej) => {
        console.log("DW#", processedNum, ": dirty work START", data)
        setTimeout(() => {
             console.log("DW#", processedNum, ": dirty work STOP ", data);
             processedNum++;
             res();
        }, 1000);
    });
}

关于node.js - 可以让事件处理程序等到异步/基于 Promise 的代码完成吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63749853/

相关文章:

javascript - promise -如何处理已知错误

javascript - 什么时候需要创建延迟?

javascript - 如何从 promise 中获得结果值(value)?

node.js - Azure DevOps 自定义任务扩展 : powershell. exe/node.exe 退出代码:5

javascript - 将无限循环中的等待转换为原始 promise.then

javascript - 如何在 javascript 中使用 .reduce() 链接带有参数的 promise /函数数组

express - 如何优雅地处理 express 中的 promise rejection

javascript - 即使使用 extraData={this.state},PureComponent FlatList 也不会重新渲染

node.js - 如何用另一个 Node 脚本调用脚本?

javascript - Node.js 以管理员身份启动程序