jfr*_*d00 5 javascript readline node.js promise async-iterator
这是我在 node v14.4.0 中提炼为最小的、可重现的示例的更大过程的一部分。在此代码中,它从for循环内部不输出任何内容。
我在控制台中只看到这个输出:
before for() loop
finished
finally
done
Run Code Online (Sandbox Code Playgroud)
该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);
})
Run Code Online (Sandbox Code Playgroud)
但是,如果我删除任何一个await once(stream, 'open')语句,那么for循环就会完全按照预期执行(列出rl1文件的所有行)。因此,显然,来自它和流之间的 readline 接口的异步迭代器存在一些计时问题。任何想法可能会发生什么。知道是什么导致了这个问题或如何解决它吗?
仅供参考,这await once(stream, 'open')是因为异步迭代器中的另一个错误,如果打开文件出现问题,它不会拒绝,而如果无法打开文件,则会await once(stream, 'open')导致您正确拒绝(基本上是预先打开) .
如果您想知道为什么存在 stream2 代码,它在更大的项目中使用,但我已将此示例缩减为最小的、可重现的示例,并且只需要这么多代码来演示问题。
编辑:在尝试稍微不同的实现时,我发现如果我将两个once(stream, "open")调用合并到a 中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);
});
Run Code Online (Sandbox Code Playgroud)
这显然不应该对您等待文件打开的确切方式敏感 某处存在一些计时错误。我想在 readline 或 readStream 上找到该错误并将其归档。有任何想法吗?
事实证明,潜在的问题是readline.createInterface(),在调用它时会立即添加一个data事件侦听器(此处的代码参考)并恢复流以开始流流动。
input.on('data', ondata);
Run Code Online (Sandbox Code Playgroud)
和
input.resume();
Run Code Online (Sandbox Code Playgroud)
然后,在ondata侦听器中,它解析行的数据,当它找到一行时,它会在此处触发一个line事件。
for (let n = 0; n < lines.length; n++)
this._onLine(lines[n]);
Run Code Online (Sandbox Code Playgroud)
但是,在我的示例中,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);
});
Run Code Online (Sandbox Code Playgroud)
解决此一般问题的一种方法是进行更改readline.createInterface(),使其不添加data事件并恢复流,直到有人添加了line事件侦听器。这将防止数据丢失。它将允许 readline 接口对象安静地坐在那里而不会丢失数据,直到其输出的接收器实际上准备就绪。这将适用于异步迭代器,并且还可以防止混入其他异步代码的接口的其他用途可能丢失line事件。
注意这个加入到相关的readline开放错误的问题在这里。