为什么这个 readline 异步迭代器无法正常工作?

2024-03-17

这是一个更大流程的一部分,我在节点 v14.4.0 中将其提炼为最小的、可重现的示例。在此代码中,它从内部不输出任何内容for loop.

我在控制台中只看到这个输出:

before for() loop
finished
finally
done

The for await (const line1 of rl1)循环永远不会进入for循环 - 它只是跳过它:

const fs = require('fs');
const readline = require('readline');
const { once } = require('events');

async function test(file1, file2) {
    try {
        const stream1 = fs.createReadStream(file1);
        await once(stream1, 'open');
        const rl1 = readline.createInterface({input: stream1, crlfDelay: Infinity});

        const stream2 = fs.createReadStream(file2);
        await once(stream2, 'open');
        const rl2 = readline.createInterface({input: stream2, crlfDelay: Infinity});

        console.log('before for() loop');
        for await (const line1 of rl1) {
            console.log(line1);
        }
        console.log('finished');
    } finally {
        console.log('finally');
    }
}

test("data/numbers.txt", "data/letters.txt").then(() => {
    console.log(`done`);
}).catch(err => {
    console.log('Got rejected promise:', err);
})

但是,如果我删除其中任何一个await once(stream, 'open')语句,那么for循环完全按照预期执行(列出了rl1文件)。因此,显然,来自 readline 接口和流之间的异步迭代器存在一些计时问题。任何想法可能会发生什么。知道什么可能导致这个问题或如何解决它吗?

仅供参考,await once(stream, 'open')是否存在因为异步迭代器中的另一个错误,如果打开文件时出现问题,它不会拒绝,而await once(stream, 'open')如果文件无法打开(本质上是在打开之前进行预检),则会导致您正确地收到拒绝。

如果您想知道为什么存在 Stream2 代码,它是在较大的项目中使用的,但我已将此示例简化为最小的、可重现的示例,并且只需要这么多代码来演示该问题。


Edit:在尝试稍微不同的实现时,我发现如果我将两者结合起来once(stream, "open")调用一个Promise.all(),然后它就可以工作了。所以,这有效:

const fs = require('fs');
const readline = require('readline');
const { once } = require('events');


async function test(file1, file2) {
    try {
        const stream1 = fs.createReadStream(file1);
        const rl1 = readline.createInterface({input: stream1, crlfDelay: Infinity});
        const stream2 = fs.createReadStream(file2);
        const rl2 = readline.createInterface({input: stream2, crlfDelay: Infinity});
        // pre-flight file open to catch any open errors here
        // because of existing bug in async iterator with file open errors
        await Promise.all([once(stream1, "open"), once(stream2, "open")]);

        console.log('before for() loop');
        for await (const line1 of rl1) {
            console.log(line1);
        }
        console.log('finished');
    } finally {
        console.log('finally');
    }
}

test("data/numbers.txt", "data/letters.txt").then(() => {
    console.log(`done`);
}).catch(err => {
    console.log('Got rejected promise:', err);
});

这显然不应该对您等待文件打开的方式敏感。某处存在一些计时错误。我想在 readline 或 readStream 上找到该错误并将其归档。有任何想法吗?


事实证明,根本问题是readline.createInterface()立即,在调用时它会添加一个data事件监听器(代码参考这里 https://github.com/nodejs/node/blob/master/lib/readline.js#L236)并恢复流以开始流流动。

input.on('data', ondata);

and

input.resume();

然后,在ondata侦听器,它解析行数据,当找到行时,它会触发line events here https://github.com/nodejs/node/blob/master/lib/readline.js#L481.

for (let n = 0; n < lines.length; n++)
  this._onLine(lines[n]);

但是,在我的示例中,在这段时间之间还发生了其他异步事情readline.createInterface()被调用并创建了异步迭代器(它将侦听line事件)。所以,line事件正在被发出,但还没有任何东西在监听它们。

所以,要正常工作readline.createInterface()要求无论要听什么line调用后必须同步添加事件readline.createInterface()或者存在竞争条件并且line 事件可能会丢失。


在我的原始代码示例中,解决此问题的可靠方法是不调用readline.createInterface()直到我完成之后await once(...)。然后,异步迭代器将在之后同步创建readline.createInterface()叫做。

const fs = require('fs');
const readline = require('readline');
const { once } = require('events');


async function test(file1, file2) {
    try {
        const stream1 = fs.createReadStream(file1);
        const stream2 = fs.createReadStream(file2);
        // wait for both files to be open to catch any "open" errors here
        // since readline has bugs about not properly reporting file open errors
        // this await must be done before either call to readline.createInterface()
        // to avoid race conditions that can lead to lost lines of data
        await Promise.all([once(stream1, "open"), once(stream2, "open")]);

        const rl1 = readline.createInterface({input: stream1, crlfDelay: Infinity});
        const rl2 = readline.createInterface({input: stream2, crlfDelay: Infinity});

        console.log('before for() loop');
        for await (const line1 of rl1) {
            console.log(line1);
        }
        console.log('finished');
    } finally {
        console.log('finally');
    }
}

test("data/numbers.txt", "data/letters.txt").then(() => {
    console.log(`done`);
}).catch(err => {
    console.log('Got rejected promise:', err);
});

解决这个普遍问题的一种方法是改变readline.createInterface()这样它就不会添加data事件并恢复流,直到有人添加line事件监听器。这可以防止数据丢失。它将允许 readline 接口对象安静地坐在那里,而不会丢失数据,直到其输出的接收器真正准备好为止。这适用于异步迭代器,并且还可以防止混合了其他异步代码的接口的其他使用可能会丢失line events.

请注意将此添加到相关的开放阅读行错误问题中here https://github.com/nodejs/node/issues/34219#issuecomment-657921958.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么这个 readline 异步迭代器无法正常工作? 的相关文章

  • JavaScript 中的可选参数

    如果缺少剩余参数 为什么该函数不会抛出错误 showStatistics Mark Teixeira New York Yankees 1st Base 这是定义的函数 function showStatistics name team p
  • webrtc - 获取网络摄像头的宽高比

    我正在尝试学习如何开发 webRTC 应用程序 我想知道是否可以获得相机的宽高比 我不知道它是否有帮助 但我正在使用 webrtc io 但是 if更好 我可以停止使用它 From MDN https developer mozilla o
  • 匹配 JavaScript RegEx 中的不可见字符

    我有一些包含不可见字符的字符串 但它们位于可预测的位置 通常 围绕我想要提取的文本片段 然后在第二次出现之后我想保留文本的其余部分 我似乎不知道如何关闭隐形字符 and将它们从我的结果中排除 为了匹配隐形 我一直在使用这个正则表达式 xA0
  • 无法使用 MV3 从 Firefox 下的通用脚本导出到 background.js

    我试图在服务工作者 background js 和内容脚本之间重用一些功能 https stackoverflow com questions 73421706 how to reuse a javascript function betw
  • 如何使用 QuerySelector 获得第二个匹配项?

    以下语句给出了该类的第一个元素titanic element document querySelector titanic 我如何检索具有相同类的第二个元素 Use document querySelectorAll https devel
  • 递归链接 Promise

    我正在开发一个简单的 Windows 8 应用程序 我需要在其中从网站获取一组数据 我正在使用 WinJS xhr 来检索此数据 它返回一个 Promise 然后 我将回调传递到此 Promise 的 then 方法中 该方法为我的回调提供
  • 角度引导手风琴数据绑定问题

    我有 2 个相同型号的下拉菜单 一个位于手风琴内部 另一个位于外部 外部下拉菜单在 2 路数据绑定方面工作良好 但手风琴内部的下拉菜单似乎只有 1 路绑定 换句话说 在 UI 中选择并不会设置模型值 我找到了一个建议here https s
  • Node.js 循环发送 http 请求

    我实际上遇到了使用 node js 执行的 javascript 代码的问题 我需要循环发送http请求到远程服务器 我在代码中设置了www google ca 这是我的代码 var http require http var option
  • 如何使用 Eclipse 作为 Javascript IDE?

    我从官方下载页面下载了 eclipse Javascript IDE 但是 当我启动应用程序时 它显示 必须提供 Java 运行时环境或 Java 开发工具包才能运行 eclipse 我已经有一个运行良好的 eclipse 的 java I
  • Ember:命名出口错误

    我不知道为什么我的模板没有在指定的插座中呈现 这是我第一次尝试学习 ember 我被困在指定的渠道上 我想渲染侧边栏模板 in the outlet sidebar 和内容模板 in the outlet content 但我不断在控制台中
  • 在express中root后通过可选参数传递路由控制?

    我正在开发一个简单的网址缩短应用程序 并有以下快速路线 app get function req res res render index link null app post function req res function makeR
  • JQuery 验证不起作用

    我有一种表单 其中一个输入类型的值为 名字 但这可以在 onfocus 函数上更改我想验证此输入字段 如果它为空白或 名字 我有两个 jQuery 文件jquery 1 4 2 min js jquery validate pack js
  • jqPlot DateAxis tickInterval 不起作用

    我试图每月绘制一个包含单个数据点的图表 我会在每个月的第一天将其作为一个点发送到 jqPlot jqplot actualChart 2011 10 01 0 296 2011 11 01 0 682 title programSelect
  • Chrome:window.print() 打印对话框仅在页面重新加载后打开 (javascript)

    我面临着一个非常奇怪的问题 我正在从 javascript 文件调用 window print 这在 Safari IE Firefox 中运行良好 直到两小时前 它在 Chrome 中也运行良好 版本29 0 1547 57 我没有更改我
  • 将命令行参数传递给 emscripten 生成的应用程序

    当使用 Emscripten 编译 C 程序时 会生成一个 HTML 页面来显示程序的结果 我想知道如何将命令行参数传递给应用程序 例如 对于原始的 C 程序 它是 bfs 32 1 我能够通过向生成的 html 文件添加一行来传递命令行参
  • 使用 sockjs 和 stomp 进行 /info 请求期间没有 cookie

    我正在尝试将 Spring Security 与 websockets 一起使用 作为一个例子 我正在使用 spring websocket chat https github com salmar spring websocket cha
  • 如何使用 Ajax 对 Oracle APEX 表格表单执行逐行验证?

    使用我执行的相同验证 处理 基于此线程 在 Oracle APEX v4 2 2 中通过 Ajax 调用 Oracle 函数进行现场验证 https stackoverflow com questions 21474303 calling
  • 如何使用正则表达式解析 OCC 选项符号?

    OCC 选项符号由 4 部分组成 标的股票或 ETF 的根代码 用空格填充至 6 个字符 到期日期 6 位数字 格式为 yymmdd 期权类型 P 或 C 用于看跌或看涨期权 执行价格 为价格 x 1000 前面填充 0 至 8 位数字 举
  • 我应该采取什么圆角方法?

    因此 关于圆角的信息并不缺乏 我已经经历过其中的大部分 我发帖是为了征求社区对这一点的意见 我的场景是 我们正在开发一个圆角相关设计 主要用于交互
  • 如何设置 .eslintrc 来识别“require”?

    我是新来的ESLint http eslint org 并且我已经成功地将 ESLint 与IntelliJ https www jetbrains com idea 开箱即用 我的 ESLint 集成无法识别node 但对文档的基本审查表

随机推荐