Skip to content

Add non-destroying AsyncIterator to Readable streams #38491

Closed

Description

@Linkgoron

Is your feature request related to a problem? Please describe.
The current async-iterator on Readable streams always destroys the stream, when you break or return or an error is thrown. Specifically, this means that if someone wants to iterate over the same stream in different places (for example iterate until the end of part one... then do some things, continue iterating later etc) this is impossible - you have to have exactly one for await...of that you work inside of. In addition, there are other scenarios like Request where iterating over the request ends up closing the response (which might specifically be a bug in the Request iterator, and not really related to destroying the request) and this suggestion would allow bypassing it for other use-cases without adding special specific use-case code (I'm not sure if this is an issue with Duplex streams in general?).

Describe the solution you'd like
I'd like to create another async-iterator (in addition to the existing one) that would give users the option opt in/out of destroying the stream non-error ending of the iteration (any maybe also on stream-error?). The naming could be very explicit, to let users know that they've decided to opt-out of the default behavior, or if an explicit name is not required just added as parameters to the Symbol.asyncIterator method.

Something like this (I haven't thought a lot about the naming):

for await (const chunk of readable.nonDestroyingIterator({ destroyOnError: true, destroyOnReturn: false}) {
}

Looking around createAsyncIterator in readable.js, this wouldn't be too difficult to add, and I would be happy to provide a PR for this.

Describe alternatives you've considered
Building another iterator myself on existing streams.

Activity

  1. added
    streamIssues and PRs related to Node.js streams.
    feature requestIssues requesting new Node.js features.
    on Apr 30, 2021
  2. addaleax commented on May 1, 2021

    @addaleax
    Member

    Definitely 👍 to this, I've also been in situations where I've piped a stream to a dummy PassThrough just so I could iterate over it without destroying the original stream.

  3. misos1 commented on May 2, 2021

    @misos1

    In addition, there are other scenarios like Request where iterating over the request ends up closing the response

    Actually Request is not destroyed when for await does not break or throw (but for example file stream is) if you meant that:

    let http = require("http");
    
    let server = http.createServer(async function(req, res)
    {
    	for await (let chunk of req)
    	{
    		await new Promise(r => setTimeout(r, 1000));
    	}
    	console.log("destroyed", req.destroyed);
    	res.end("ok");
    });
    
    (async function()
    {
    	await new Promise(resolve => server.listen(resolve));
    	let req = http.request({ port: server.address().port, method: "post" }).end("abc");
    	let res = await new Promise((resolve, reject) => req.on("response", resolve).on("error", reject));
    	let str = "";
    	for await (let chunk of res) str += chunk;
    	console.log("client received:", str);
    	server.close();
    }());
    destroyed false
    client received: ok
    
  4. ronag commented on May 2, 2021

    @ronag
    Member
    for await (const chunk of readable.iterator({ destroyOnError: true, destroyOnReturn: false }) {
    }
  5. ronag commented on May 3, 2021

    @ronag
    Member

    Because it's a generic method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestIssues requesting new Node.js features.streamIssues and PRs related to Node.js streams.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions