如何在Node.js流回调中聚合从异步函数生成的promise?

时间:2016-02-19 18:35:35

标签: javascript node.js stream typescript promise

我有一个Node.js Typescript程序,我正在尝试逐行解析大型CSV文件并异步处理这些行。更具体地说,我需要一个能够:

的功能
  1. 打开CSV文件。
  2. 将下一行解析为对象。
  3. (理想情况下)收集一定数量的对象进行批处理。
  4. 将对象传递给异步函数进行处理(返回一个promise)。
  5. 从处理函数中收集承诺。
  6. 一些要求和注意事项:

    • 我需要轮询这些承诺以取得进展。
    • 假设这些CSV文件很大;逐行流式传输是必要的。
    • 我不应该在这些处理操作正在运行时阻止应用程序。
    • 返回一系列承诺可能不是正确的方法,特别是如果我正在尝试读取一百万行的文件。
    • 我需要一个排序来取消或重试失败的操作。

    这是我已经开始工作的一些测试代码。 ObjectStream是一个自定义Node.js变换,可将CSV行转换为对象。

    function parseFileAsync(filePath: string): Promise<any> {
        var doParseFileAsync = (filePath: string) => {
            var streamDeferred = q.defer<Promise<any>[]>();
            var promises: Promise<any>[] = [];
            var propertyNames: string[] = [];
    
            var stream = fs.createReadStream(filePath, { encoding: "utf8" })
                .pipe(new LineStream({ objectMode: true }))
                .pipe(new ObjectStream({ objectMode: true }));
    
            stream.on("readable", () => {
                var obj: Object;
                while ((obj = stream.read()) !== null) {
                    console.log(`\nRead an object...`);
    
                    var operationDeferred = q.defer<any>();
                    operationDeferred.resolve(doSomethingAsync(obj));
                    promises.push(operationDeferred.promise);
                }
            });
            stream.on("end", () => {
                streamDeferred.resolve(promises);
            });
    
            return streamDeferred.promise;
        }
    
        return doParseFileAsync(filePath)
            .then((result: Promise<any>[]) => {
                return q.all(result);
            });
    }
    parseFileAsync(filePath)
        .done((result: any[]) => {
            console.log(`\nFinished reading and processing the file:\n\t${result.toString()}`);
        });
    

    最后done调用在流开始运行之前执行,因为parseFileAsync立即满足空数组;流还没有机会推动任何承诺。

    经过几天的搜索,我仍然不确定这样做的正确方法是什么。节点/ JavaScript专家:帮助?

    更新

    代码已经更新,我的承诺现在正在发挥得非常好。但是,如果需要,我需要一种方法来挂钩流并取消该过程。我还需要一种方法来重试失败的任何操作。

1 个答案:

答案 0 :(得分:1)

我在程序的架构中遇到了一些限制,这些限制不允许我像我想的那样自由地传递承诺。相反,我决定等到上一批完成后再开始新的承诺,而不是开始一堆承诺。这是我采取的方法:

  1. 将流内容分隔为自己接受连续令牌的函数。如果要读取更多数据,返回值将包含读取的数据以及延续令牌:

    function readFile(filepath: string, lines: number, start: any): Promise<any> {
        ...
    }
    
  2. 定义将运行可重试操作的函数。在此函数的主体内,从文件中检索并处理一大块数据。如果结果具有延续令牌,则“递归地”再次调用操作函数:

    function processFile(filepath: string, next: any): Promise<any> {
        var chunkSize = 1;
        return readLines(filepath, chunkSize, next)
            .then((result) => {
                // Do something with `result.lines`
                ...
                if (result.next) {
                    return parseFile(filepath, result.next);
                }
            });
    }
    
  3. 瞧!长期运行的操作,可以在块上运行,并且很容易报告进度。