Node.js 是否可以让事件处理程序等待异步/基于承诺的代码完成?

Node.js 是否可以让事件处理程序等待异步/基于承诺的代码完成?,node.js,promise,es6-promise,node-streams,backpressure,Node.js,Promise,Es6 Promise,Node Streams,Backpressure,我在nodejs模式下使用优秀的Papa解析库,将一个超过100万行的大型(500 MB)CSV文件流式传输到一个缓慢的持久性API中,一次只能接受一个请求。持久性API基于Promises,但从Papa Parse,我在同步事件中接收每个解析的CSV行,如下所示:parseStream.on(“数据”,行=>{…} 我面临的挑战是Papa Parse从流中转储其CSV行的速度如此之快,以至于我缓慢的持久性API无法跟上。因为Papa是同步的,而我的API是基于承诺的,所以我不能只调用等待doD

我在nodejs模式下使用优秀的Papa解析库,将一个超过100万行的大型(500 MB)CSV文件流式传输到一个缓慢的持久性API中,一次只能接受一个请求。持久性API基于
Promise
s,但从Papa Parse,我在同步事件中接收每个解析的CSV行,如下所示:
parseStream.on(“数据”,行=>{…}

我面临的挑战是Papa Parse从流中转储其CSV行的速度如此之快,以至于我缓慢的持久性API无法跟上。因为Papa是同步的,而我的API是基于承诺的,所以我不能只调用
等待doDirtyWork(行)
事件处理程序上的
中,因为同步和异步代码不会混合

或者他们可以混合,而我只是不知道如何混合

我的问题是,我能让Papa的事件处理程序等待API调用完成吗?有点像是直接在
on(“数据”)
事件中执行持久性API请求,使
on()
函数以某种方式徘徊,直到脏API工作完成

就内存占用而言,到目前为止,我的解决方案并不比使用Papa的非流模式好多少。实际上,我需要排队等待(“数据”)上的
洪流
事件,以生成器函数迭代的形式。我还可以将承诺工厂排在一个数组中,并在一个循环中进行处理。不管怎样,我最终都会将几乎整个CSV文件作为未来承诺(承诺工厂)的巨大集合保存在内存中,直到我缓慢的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);
}
异步导入SV(文件路径){
让parsedNum=0,processedNum=0;
异步函数*gen(){
设pf=产量;
做{
pf=收益率等待pf();
}而(pf的类型==“函数”);
};
var g=gen();
g、 next();
等待新的承诺((决定,拒绝)=>{
试一试{
const dataStream=fs.createReadStream(文件路径);
const parseStream=Papa.parse(Papa.NODE_STREAM_输入,{分隔符:“,”,头:false});
管道(parseStream);
parseStream.on(“数据”,行=>{
//从Papa.parse()接收到CSV行
试一试{
console.log(“PA#”,parsedNum,”:parsed“,row.filter((e,i)=>i{//现在不执行,通过上面的生成器按顺序调用
返回新承诺((res,rej)=>{
console.log(
“DW#”,processedNum,“:脏工作开始”,
行过滤器((e,i)=>i{
console.log(
“DW#”,processedNum,“:脏工停止”,
行过滤器((e,i)=>i{
log(`Parsed${parsedNum}行`);
解决();
});
}捕捉(错误){
console.log(err.stack);
拒绝(错误);
}
});
而(!(等待g.next()).done);
}
那么为什么要赶时间爸爸?为什么不让我把文件慢一点——原始CSV文件中的数据不会流失,我们有几个小时的时间来完成流媒体,为什么要在(“数据”)上敲打我
我似乎不能慢下来的事件

因此,我真正需要的是让Papa更像爷爷,最小化或消除CSV行的任何排队或缓冲。理想情况下,我能够以API的速度(或缺少速度)完全同步Papa的解析事件。因此,如果不是因为异步代码不能使同步代码“休眠”的教条,理想情况下,我会将每个CSV行发送到Papa事件中的API然后将控制权返回给Papa


建议?某种“松耦合”我的异步API速度慢的事件处理程序也很好。我不介意几百行排队。但当成千上万的行堆积起来时,我会很快用完堆。

JavaScript中的异步代码有时有点难以理解。记住Node如何操作和处理并发性很重要

节点进程是单线程的,但它使用了一个称为事件循环的概念,其结果是异步代码和回调本质上是同一事物的等价表示

当然,您需要一个异步函数来使用
await
,但是Papa Parse的回调可以是一个异步函数:

parse.on("data", async row => {
  await sync(row)
})
一旦
wait
操作完成,arrow函数结束,所有对row的引用都将被删除,因此垃圾收集器可以成功地收集
row
,释放该内存

这样做的效果是每次解析一行时都会并发执行
sync
,因此如果一次只能同步一条记录,那么我建议将sync函数包装在一个去Bouncer中

为什么要用
“数据”
来重击我似乎无法减慢速度的事件

你可以,你只是没有要求爸爸停下来。你可以通过打电话来做到这一点,然后再利用节点流的内置背压

但是,有一个比在基于回调的代码中自己处理此问题更好的API可供使用:!当您在
for await
循环体中执行
wait
时,生成器也必须暂停。因此您可以编写

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);
    });
}
异步导入SV(文件路径){
设parsedNum=0;
const dataStream=fs.createReadStream(文件路径);
const parseStream=Papa.parse(Papa.NODE_STREAM_输入,{分隔符:“,”,头:false});
管道(parseStream);
用于等待(parseStream的常量行){
//从Papa.parse()接收到CSV行
常量数据=行过滤器((e,i)=>i{
log(“DW#”,processedNum,”:脏工作开始,数据)
塞蒂