【问题标题】:Node.js stream: stream appears freezedNode.js 流:流出现冻结
【发布时间】:2022-02-27 04:35:46
【问题描述】:

我想创建一个简单的 csv 解析器(使用 csv 模块)并在文件不存在时处理错误。

如果我注释掉 sleep 方法,代码会到达 finally(并写出一些错误)。

我错过了什么?在我的真实示例中,我确实需要在那里完成一些等待的任务。

import fs from 'fs';
import * as csv from 'csv';
import { finished } from 'stream';

type TabularFileType = 'csv' | 'tsv';

const DELIMITER_BY_TYPE: Record<TabularFileType, string> = {
    csv: ',',
    tsv: '\t',
};

/**
 * Creates an (async) iterable from a tabular file at `path`
 * @param type type of file, could be `'csv'` or `'tsv'`
 * @param path path of the file to be loaded
 * @param columns an array of string defining the property names of the columns
 * @param options can specify some optional things,
 * like encoding (will decode using iconv-lite)
 * and skip some header lines (with the help of `fromLines`)
 */
export function tabularStream<T>(
    type: TabularFileType,
    path: string,
    columns: string[],
    options?: { encoding?: string; fromLine?: number }
): csv.parser.Parser {
    console.info('About to read file from "%s"', path);
    const stream = fs.createReadStream(path);

    const result = stream.pipe(
        csv.parse({
            delimiter: DELIMITER_BY_TYPE[type],
            fromLine: options?.fromLine,
            columns,
        })
    );

    finished(stream, (err) => {
        if (err) {
            result.destroy(new Error('whoops'));
        }
    });

    console.log("About to return");
    return result;
}

const sleep = (millis: number) => new Promise(resolve => setTimeout(resolve, millis));

(async () => {
    console.log('wait a bit');
    await sleep(100);
    console.log('waited');
    const p = tabularStream('csv', 'nonexistent', []);
    // If you comment it out, ended will appear on output
    await sleep(100);
    console.log('after sleep')  ;
    try {
        for await(const record of p) {
            console.log('Got a record', record);
        }
    } catch (e) {
        console.log('Some error!', e);
    } finally {
        console.log('Finally!');
    }
})();

【问题讨论】:

  • 我无法重现任何问题。使用 ts-node v10.2.1 和 node v16.13.0。但我建议将对 tabularStream 的调用放在 try 块内。
  • 是否可以提供 .csv 文件的摘录作为要点,以便问题可重现?
  • @jorgenkg: 不存在:所以没有这样的文件。代码完成。
  • 由于stream 变量缺少"error" 侦听器,因此代码中断。该错误不会传播到随后的管道流中间件。但是,可以将代码重构为使用stream.pipeline(),它隐式地同时管道数据和处理错误。
  • @jorgenkg 为什么不将您的评论转换为答案?

标签: node.js typescript csv stream


【解决方案1】:

与直觉相反,每个流中间件都需要自己的"error" 侦听器来正确处理流错误。

stream 变量在 OP 的 sn-p 中缺少错误侦听器。因此,读取不存在的文件时会引发未处理的(异步)错误。

export function tabularStream(...) {
    const stream = fs.createReadStream(path); // <-- stream needs an "error" listener
    const result = stream.pipe(csv.parse(...));
    ...
}

(取消)注释“睡眠”步骤通过尽早而不是稍后评估异步、未处理的错误来改变 sn-p 的执行方式。

nodejs 无需在所有流中间件上包含 "error" 侦听器,而是提供了一个处理管道数据和错误的 stream.pipeline() 函数。

export function tabularStream(...) {
  const result: stream.Readable = stream.pipeline(
    fs.createReadStream(path),
    csv.parse(...),
    error => (error && result.emit("error", error))
  );
  ...
  return result;
} // remember to add `error` listener to the returned variable 

【讨论】:

  • 只是一件我不明白的小事:为什么要保护error &amp;&amp;?可以用null/undefined调用错误处理程序吗?
  • 不,这仍会被视为错误。在 NodeJS 的事件驱动代码中发出 "error" 相当于在过程代码中抛出错误。所有可能发出"error" 事件的EventEmitters 都必须有一个错误监听器;否则,将导致 UnhandledException。个人意见:按照惯例,我永远不会throw null,也不会发出带有空对象的错误事件。
猜你喜欢
  • 2021-12-24
  • 2017-08-09
  • 2020-06-28
  • 2015-03-21
  • 2015-04-28
  • 2013-09-13
  • 1970-01-01
  • 2019-12-01
  • 2021-06-30
相关资源
最近更新 更多