-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
readline: add support for async iteration #18904
readline: add support for async iteration #18904
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work, LGTM
const file_content = 'abc123\n' + | ||
'南越国是前203年至前111年存在于岭南地区的一个国家\n'; | ||
|
||
fs.writeFile(filename, file_content); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this really be writeFileSync()
to avoid any potential race conditions?
tests().then(common.mustCall()); | ||
|
||
process.on('exit', function() { | ||
fs.unlinkSync(filename); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this instead be moved to the common.mustCall()
callback?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be removed entirely. Test files do not need to clean up the temp directory. Tests that use the temp directory clean it up themselves at the start of the test with tmpdir.refresh()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let data = ''; | ||
for await (const k of rli) { | ||
data += k; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be nice to check how many iterations of the loop there were
|
||
const common = require('../common'); | ||
const fs = require('fs'); | ||
const join = require('path').join; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A nit: destruction?
fs.writeFile(filename, file_content); | ||
|
||
async function tests() { | ||
await (async function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A nit: arrow function?
Just a tiny doubt: the |
@vsemozhetbyt By demand you mean explicitly mention this in documentation? |
lib/readline.js
Outdated
@@ -239,6 +241,14 @@ Interface.prototype.setPrompt = function(prompt) { | |||
}; | |||
|
|||
|
|||
Interface.prototype[Symbol.asyncIterator] = function() { | |||
emitExperimentalWarning('Interface[Symbol.asyncIterator]'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: in a project it may not be clear what Interface
is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm with nits
@prog1dev Yes, documented advice. |
@prog1dev I've compiled the branch and tested the slightly edited doc example code on Windows 7 x64.
'use strict';
const readline = require('readline');
const fs = require('fs');
async function processLineByLine(readable) {
// readable.setEncoding('utf8');
const rli = readline.createInterface({
input: readable,
crlfDelay: Infinity
});
let i = 0;
for await (const line of rli) {
console.log(`Line ${++i}: ${line}`);
}
}
processLineByLine(fs.createReadStream(__filename)).catch(console.error); (node:3148) ExperimentalWarning: Interface[Symbol.asyncIterator] is an experimental feature. This feature could change at any time
Line 1: 'use strict';
const readline = require('readline');
const fs = require('fs');
async function processLineByLine(readable) {
// readable.setEncoding('utf8');
const rli = readline.createInterface({
input: readable,
crlfDelay: Infinity
});
let i = 0;
for await (const line of rli) {
console.log(`Line ${i}: ${line}`);
}
}
processLineByLine(fs.createReadStream(__filename)).catch(console.error); Dear reviewers, please chime in. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in @vsemozhetbyt’s example, the added method doesn’t work. readline emits 'line'
events that needed to be listened for, not 'data'
as is the case for readable streams. As such, the general stream async iterator cannot be used for readline.
The PR just gets an async iterator of the input stream. That completely skips the readline mechanism, and as such does not work.
@mcollina Looks like no matter how much lines file has there is only one iteration of
Is this behaviour intentional? Its different for readable streams in object mode |
It seems the longing TBH, when I started to learn Node.js, using But this is a pretty basic tool for text file processing to be outsourced to more handy userland modules. And this async API would be a big step towards making |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 based on @vsemozhetbyt exmaple.
I'm pretty sure this is is because of internal buffering. You should try reading a different file. This is an experimental feature, and I would be surprise if it does not have bugs. Feel free to send PRs to fix those. |
Ping @prog1dev |
@BridgeAR Im working on this in my spare time |
e3532d8
to
3468ed9
Compare
716e6f1
to
2f5c558
Compare
return null; | ||
|
||
return this._buffer.shift(); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this function private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean to rename it to _read
?
|
||
this._enableBuffer = true; | ||
this.close(); | ||
this.input.setEncoding('utf8'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's definitely not super friendly when a module like readline changes stream settings, especially when we only do it with async iteration. We should add this feature without it.
emitExperimentalWarning('readline Interface[Symbol.asyncIterator]'); | ||
|
||
this._enableBuffer = true; | ||
this.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We definitely should not close the readline interface when we start iterating. This will emit events like 'close'
that can be very much misleading to users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order for it to work stream must be in paused mode so I can get data with stream.read()
calls. So instead of closing I guess I can pause it with this.input.pause()
Interface.prototype[Symbol.asyncIterator] = function() { | ||
emitExperimentalWarning('readline Interface[Symbol.asyncIterator]'); | ||
|
||
this._enableBuffer = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this ever getting restored after the completion of iteration, and I feel like it should be. Correct me if I'm wrong :)
There is still a lot of logic shared between this[kLastResolve] = null;
this[kLastReject] = null;
this[kError] = null;
this[kEnded] = false;
this[kLastPromise] = null; chunk in the constructor, and also the |
@@ -77,6 +80,8 @@ function Interface(input, output, completer, terminal) { | |||
this.isCompletionEnabled = true; | |||
this._sawKeyPress = false; | |||
this._previousKey = null; | |||
this._enableBuffer = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a comment on what this does? Also, I would prefer if this was a Symbol
instead.
Let me toy with an idea.. how about we implement this on top of a |
What's the status on this? |
@jasnell Stalled. Maybe later this week I can check this |
@prog1dev Would it be okay with you if I were to take over this PR? |
@TimothyGu Sure go on |
New PR opened at #23916. |
Co-authored-by: Ivan Filenko <ivan.filenko@protonmail.com> Fixes: nodejs#18603 Refs: nodejs#18904
Co-authored-by: Ivan Filenko <ivan.filenko@protonmail.com> Fixes: nodejs#18603 Refs: nodejs#18904 PR-URL: nodejs#23916 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Gus Caplan <me@gus.host>
Co-authored-by: Ivan Filenko <ivan.filenko@protonmail.com> Fixes: nodejs#18603 Refs: nodejs#18904 PR-URL: nodejs#23916 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Gus Caplan <me@gus.host>
Sync readline API with
for-await-of
support in readable streamsResolves #18603
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
readline, stream, doc, test
@vsemozhetbyt @mcollina