GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — stream: speed up async iteration of Readable by mcollina · Pull Request #64447 · nodejs/node

Conversation

@mcollina

@mcollina mcollina commented Jul 12, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Replace the async generator backing Readable.prototype[Symbol.asyncIterator] (and .iterator()) with a hand-rolled iterator.

The generator machinery costs several extra promise allocations and microtask hops per chunk: yield awaits the yielded value and resolves the pending request through separate promises, and every next() goes through the async generator request queue. The hand-rolled iterator delivers buffered chunks as an already-resolved promise.

The observable semantics are preserved:

  • thenable chunks (object mode) are still awaited before delivery, and a rejected thenable still tears down the iterator and the stream;
  • next()/return()/throw() calls received while a request is outstanding are queued and processed in order — including return() while waiting for data, which still completes only once the pending read settles;
  • return()/throw() before the first next() complete the iterator without attaching listeners or destroying the stream;
  • the finally teardown logic (destroyOnReturn, autoDestroy, half-open duplex preservation) is unchanged;
  • error aggregation via aggregateTwoErrors is unchanged.

The one observable difference is that buffered chunks are delivered one microtask sooner than before, since the generator's yield performed an implicit Await on the yielded value. This is visible to code racing an abort against the first chunk. The flatMap AbortSignal test relied on such a race (a queueMicrotask'd abort beating the first chunk); it is reworked to be deterministic and timer-free: two mappers block until their signal aborts, the abort fires while both are in flight, and the test asserts the concurrency limit is respected (exactly two mappers start), in-flight mappers are cancelled through their signal, and iteration rejects with AbortError. The reworked test passes against both the old and the new implementation.

New regression tests cover the subtler iterator behaviors (thenable unwrapping, rejected thenables, throw(), pre-start throw(), concurrent next() ordering, and return() queued behind a pending next()); they also pass against both implementations.

Benchmark (benchmark/compare.js, 30 runs):

                                                       confidence improvement accuracy (*)   (**)  (***)
streams/readable-async-iterator.js sync='no' n=100000         ***      9.84 %       ±3.04% ±4.05% ±5.27%
streams/readable-async-iterator.js sync='yes' n=100000        ***     32.59 %       ±5.49% ±7.34% ±9.62%

No changes on pipe.js / readable-readall.js.

🤖 Generated with Claude Code

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

peterhirn reacted with heart emoji
All reactions
  • ❤️ 1 reaction
@nodejs-github-bot

nodejs-github-bot commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/streams
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams. labels Jul 12, 2026
@mcollina
stream: speed up async iteration of Readable
6f9a2f3
Replace the async generator backing Symbol.asyncIterator with a
hand-rolled iterator. The generator machinery costs several extra
promise allocations and microtask hops per chunk: yield awaits the
yielded value and resolves the pending request through separate
promises. Buffered chunks are now delivered as an already-resolved
promise, one microtask sooner than before.

Thenable chunks are still awaited before delivery, requests received
while a next() is outstanding are queued, and return()/throw() before
the first next() complete the iterator without touching the stream.

The earlier delivery is observable by code racing an abort against
the first chunk. The flatMap AbortSignal test relied on such a race;
it is reworked to abort deterministically while two mappers are in
flight, asserting the concurrency limit, in-flight cancellation and
rejection, without depending on delivery timing or timers.

streams/readable-async-iterator.js sync='yes': +32.59% (***)
streams/readable-async-iterator.js sync='no': +9.84% (***)

Assisted-by: Claude Fable 5
Signed-off-by: Matteo Collina <[email protected]>
@mcollina
mcollina force-pushed the stream-async-iterator-perf branch from 2307152 to 6f9a2f3 Compare July 12, 2026 08:36
aduh95
aduh95 reviewed Jul 12, 2026
View reviewed changes
Comment thread lib/internal/streams/readable.js Outdated
Comment on lines +1486 to +1497
if (typeof chunk.then === 'function') {
PromisePrototypeThen(PromiseResolve(chunk), (value) => {
inFlight = false;
resolve({ done: false, value });
if (queue !== null) drain();
}, (err) => {
inFlight = false;
settleError(err, reject);
if (queue !== null) drain();
});
return;
}

@aduh95 aduh95 Jul 12, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

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.

If then is a getter that e.g. throws on the second access, this code might throw when previously it wouldn't have. We can protect against that by storing the initial value we're getting (that might also avoid an additional promise allocation).

Suggested change
if (typeof chunk.then === 'function') {
PromisePrototypeThen(PromiseResolve(chunk), (value) => {
inFlight = false;
resolve({ done: false, value });
if (queue !== null) drain();
}, (err) => {
inFlight = false;
settleError(err, reject);
if (queue !== null) drain();
});
return;
}
const { then } = chunk;
if (typeof then === 'function') {
FunctionPrototypeCall(then, chunk, (value) => {
inFlight = false;
resolve({ done: false, value });
if (queue !== null) drain();
}, (err) => {
inFlight = false;
settleError(err, reject);
if (queue !== null) drain();
});
return;
}

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
aduh95
aduh95 reviewed Jul 12, 2026
View reviewed changes
Comment thread lib/internal/streams/readable.js Outdated
Comment on lines +1555 to +1558
if (typeof chunk.then === 'function') {
inFlight = true;
return PromisePrototypeThen(
PromiseResolve(chunk), onChunkFulfilled, onChunkRejected);

@aduh95 aduh95 Jul 12, 2026

Copy link
Copy Markdown
Contributor

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.

Same here

Suggested change
if (typeof chunk.then === 'function') {
inFlight = true;
return PromisePrototypeThen(
PromiseResolve(chunk), onChunkFulfilled, onChunkRejected);
const { then } = chunk;
if (typeof then === 'function') {
inFlight = true;
return FunctionPrototypeCall(then, chunk, onChunkFulfilled, onChunkRejected);

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Renegade334
Renegade334 reviewed Jul 12, 2026
View reviewed changes
Comment thread lib/internal/streams/readable.js
settleError(err, reject);
}

return {

@Renegade334 Renegade334 Jul 12, 2026

Copy link
Copy Markdown
Member

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.

This object should ideally have a prototype of AsyncIteratorPrototype.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

aduh95 reacted with thumbs down emoji
All reactions
  • 👎 1 reaction

@aduh95 aduh95 Jul 13, 2026

Copy link
Copy Markdown
Contributor

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'm not sure it should if the sprecs doesn't require it – it does add a @@asyncDispose method which may or may not be desirable – it also adds a @@asyncIterator, which begs the question whether we should re-implement the method (re-implementing it ourselves means we're not subject to prototype tampering; otherwise, letting the built-in method be inherited would saves a bit of memory maybe?)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@Renegade334 Renegade334 Jul 15, 2026

Copy link
Copy Markdown
Member

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.

The current implementation derives from AsyncIteratorPrototype (as a generator) so if we're trying to minimise observability then this is a fairly free move. (While the language currently only provides for the two well-known symbol methods, once we get into async iterator helpers territory, there will be significant advantages to keeping this inheritance.)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

MattiasBuelens reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@codecov

codecov Bot commented Jul 12, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.23256% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.29%. Comparing base (8a3b11c) to head (873a47e).
⚠️ Report is 472 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/streams/readable.js 90.23% 20 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #64447      +/-   ##
==========================================
+ Coverage   90.24%   90.29%   +0.04%     
==========================================
  Files         741      760      +19     
  Lines      241384   247324    +5940     
  Branches    45480    46652    +1172     
==========================================
+ Hits       217844   223310    +5466     
- Misses      15097    15477     +380     
- Partials     8443     8537      +94     
Files with missing lines Coverage Δ
lib/internal/streams/readable.js 96.55% <90.23%> (-0.72%) ⬇️

... and 209 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mertcanaltin
mertcanaltin reviewed Jul 12, 2026
View reviewed changes
Comment thread lib/internal/streams/readable.js Outdated
return new Promise((resolve, reject) => {
if (inFlight) {
queue ??= [];
queue.push({ type: 'return', value, resolve, reject });

@mertcanaltin mertcanaltin Jul 12, 2026

Copy link
Copy Markdown
Member

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.

Suggested change
queue.push({ type: 'return', value, resolve, reject });
queue.push({ __proto__: null, type: 'return', value, resolve, reject });

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread lib/internal/streams/readable.js Outdated
return new Promise((resolve, reject) => {
if (inFlight) {
queue ??= [];
queue.push({ type: 'next', value: undefined, resolve, reject });

@mertcanaltin mertcanaltin Jul 12, 2026

Copy link
Copy Markdown
Member

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.

Suggested change
queue.push({ type: 'next', value: undefined, resolve, reject });
queue.push({ __proto__: null, type: 'next', value: undefined, resolve, reject });

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread lib/internal/streams/readable.js Outdated
return new Promise((resolve, reject) => {
if (inFlight) {
queue ??= [];
queue.push({ type: 'throw', value: err, resolve, reject });

@mertcanaltin mertcanaltin Jul 12, 2026

Copy link
Copy Markdown
Member

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.

Suggested change
queue.push({ type: 'throw', value: err, resolve, reject });
queue.push({ __proto__: null, type: 'throw', value: err, resolve, reject });

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
mertcanaltin
mertcanaltin reviewed Jul 12, 2026
View reviewed changes
Comment thread lib/internal/streams/readable.js Outdated

function drain() {
while (!inFlight && queue.length > 0) {
const req = queue.shift();

@mertcanaltin mertcanaltin Jul 12, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

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 replace queue.shift(); with an index-based queue for high throughput?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
gurgunday
gurgunday reviewed Jul 12, 2026
View reviewed changes
Comment thread lib/internal/streams/readable.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

gurgunday
gurgunday reviewed Jul 12, 2026
View reviewed changes
Comment thread lib/internal/streams/readable.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

jasnell
jasnell reviewed Jul 12, 2026
View reviewed changes
Comment thread test/parallel/test-stream-flatMap.js Outdated
Comment on lines +84 to +89
await new Promise((resolve, reject) => {
if (signal.aborted) {
reject(signal.reason);
return;
}
signal.addEventListener('abort', () => reject(signal.reason), { once: true });

@jasnell jasnell Jul 12, 2026

Copy link
Copy Markdown
Member

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.

Suggested change
await new Promise((resolve, reject) => {
if (signal.aborted) {
reject(signal.reason);
return;
}
signal.addEventListener('abort', () => reject(signal.reason), { once: true });
const { promise, reject } = Promise.withResolvers();
if (signal.aborted) {
reject(signal.reason);
}
signal.addEventListener('abort', () => reject(signal.reason), { once: true });
// Promise is expected to reject.
await promise;

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@lukiano lukiano mentioned this pull request Jul 12, 2026
Implementation of Readable iteration helpers that does not rely on Async Iteration #64429
Closed
@mcollina
fixup: address review comments
14505b2
Signed-off-by: Matteo Collina <[email protected]>
gurgunday
gurgunday approved these changes Jul 13, 2026
View reviewed changes

@gurgunday gurgunday left a comment

Copy link
Copy Markdown
Member

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

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Jul 13, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Jul 13, 2026
@nodejs-github-bot

nodejs-github-bot commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/74788/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mertcanaltin
mertcanaltin approved these changes Jul 13, 2026
View reviewed changes
MattiasBuelens
MattiasBuelens reviewed Jul 15, 2026
View reviewed changes
Comment thread lib/internal/streams/readable.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

@mcollina
fixup: remove [SymbolAsyncIterator]
9eb1e60
Signed-off-by: Matteo Collina <[email protected]>
@mcollina

mcollina commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

@MattiasBuelens updated

MattiasBuelens reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

MattiasBuelens
MattiasBuelens approved these changes Jul 15, 2026
View reviewed changes
gurgunday
gurgunday approved these changes Jul 15, 2026
View reviewed changes

@gurgunday gurgunday left a comment

Copy link
Copy Markdown
Member

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

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mcollina
stream: fix lint in readable async iterator
873a47e
Signed-off-by: Matteo Collina <[email protected]>
ronag
ronag approved these changes Aug 2, 2026
View reviewed changes
@ronag ronag added request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. author ready PRs with CI started, the required approvals, and no outstanding review comments. labels Aug 6, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Aug 6, 2026
@nodejs-github-bot

nodejs-github-bot commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/75563/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mcollina mcollina added the commit-queue PRs queued for automated landing through the Commit Queue. label Aug 14, 2026
@nodejs-github-bot nodejs-github-bot added commit-queue-failed PRs whose Commit Queue landing failed and need manual intervention before retrying. and removed commit-queue PRs queued for automated landing through the Commit Queue. labels Aug 14, 2026
@nodejs-github-bot

nodejs-github-bot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator
Commit Queue failed
- Loading data for nodejs/node/pull/64447
✔  Done loading data for nodejs/node/pull/64447
----------------------------------- PR info ------------------------------------
Title      stream: speed up async iteration of Readable (#64447)
Author     Matteo Collina <[email protected]> (@mcollina)
Branch     mcollina:stream-async-iterator-perf -> nodejs:main
Labels     stream, author ready, needs-ci, commit-queue
Commits    4
 - stream: speed up async iteration of Readable
 - fixup: address review comments
 - fixup: remove [SymbolAsyncIterator]
 - stream: fix lint in readable async iterator
Committers 1
 - Matteo Collina <[email protected]>
PR-URL: https://github.com/nodejs/node/pull/64447
Reviewed-By: Gürgün Dayıoğlu <[email protected]>
Reviewed-By: Mattias Buelens <[email protected]>
Reviewed-By: Robert Nagy <[email protected]>
------------------------------ Generated metadata ------------------------------
PR-URL: https://github.com/nodejs/node/pull/64447
Reviewed-By: Gürgün Dayıoğlu <[email protected]>
Reviewed-By: Mattias Buelens <[email protected]>
Reviewed-By: Robert Nagy <[email protected]>
--------------------------------------------------------------------------------
   ℹ  This PR was created on Sun, 12 Jul 2026 08:27:10 GMT
   ✔  Approvals: 3
   ✔  - Gürgün Dayıoğlu (@gurgunday): https://github.com/nodejs/node/pull/64447#pullrequestreview-4707049635
   ✔  - Mattias Buelens (@MattiasBuelens): https://github.com/nodejs/node/pull/64447#pullrequestreview-4706259826
   ✔  - Robert Nagy (@ronag) (TSC): https://github.com/nodejs/node/pull/64447#pullrequestreview-4838491319
   ✔  Last GitHub CI successful
   ℹ  Last Full PR CI on 2026-08-06T13:07:45Z: https://ci.nodejs.org/job/node-test-pull-request/75563/
- Querying data for job/node-test-pull-request/75563/
✔  Build data downloaded
   ✔  Last Jenkins CI successful
--------------------------------------------------------------------------------
   ✔  No git cherry-pick in progress
   ✔  No git am in progress
   ✔  No git rebase in progress
--------------------------------------------------------------------------------
- Bringing origin/main up to date...
From https://github.com/nodejs/node
 * branch                  main       -> FETCH_HEAD
✔  origin/main is now up-to-date
- Downloading patch for 64447
From https://github.com/nodejs/node
 * branch                  refs/pull/64447/merge -> FETCH_HEAD
✔  Fetched commits as 9e23066b8af4..873a47ef6ae8
--------------------------------------------------------------------------------
Auto-merging lib/internal/streams/readable.js
[main 0a0be7f763] stream: speed up async iteration of Readable
 Author: Matteo Collina <[email protected]>
 Date: Sat Jul 11 23:41:55 2026 +0200
 3 files changed, 307 insertions(+), 35 deletions(-)
Auto-merging lib/internal/streams/readable.js
[main 22401b1cb5] fixup: address review comments
 Author: Matteo Collina <[email protected]>
 Date: Mon Jul 13 10:00:44 2026 +0200
 3 files changed, 73 insertions(+), 26 deletions(-)
Auto-merging lib/internal/streams/readable.js
[main 0b186829dd] fixup: remove [SymbolAsyncIterator]
 Author: Matteo Collina <[email protected]>
 Date: Wed Jul 15 17:34:22 2026 +0200
 1 file changed, 1 insertion(+), 4 deletions(-)
Auto-merging lib/internal/streams/readable.js
[main 656bc697b3] stream: fix lint in readable async iterator
 Author: Matteo Collina <[email protected]>
 Date: Sun Aug 2 09:08:31 2026 +0000
 1 file changed, 1 insertion(+), 1 deletion(-)
   ✔  Patches applied
There are 4 commits in the PR. Attempting autorebase.
(node:388) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.
(Use `node --trace-deprecation ...` to show where the warning was created)
Rebasing (2/8)
Executing: git node land --amend --yes
--------------------------------- New Message ----------------------------------
stream: speed up async iteration of Readable

Replace the async generator backing Symbol.asyncIterator with a
hand-rolled iterator. The generator machinery costs several extra
promise allocations and microtask hops per chunk: yield awaits the
yielded value and resolves the pending request through separate
promises. Buffered chunks are now delivered as an already-resolved
promise, one microtask sooner than before.

Thenable chunks are still awaited before delivery, requests received
while a next() is outstanding are queued, and return()/throw() before
the first next() complete the iterator without touching the stream.

The earlier delivery is observable by code racing an abort against
the first chunk. The flatMap AbortSignal test relied on such a race;
it is reworked to abort deterministically while two mappers are in
flight, asserting the concurrency limit, in-flight cancellation and
rejection, without depending on delivery timing or timers.

streams/readable-async-iterator.js sync='yes': +32.59% ()
streams/readable-async-iterator.js sync='no': +9.84% (
)

Assisted-by: Claude Fable 5
Signed-off-by: Matteo Collina <[email protected]>
PR-URL: #64447
Reviewed-By: Gürgün Dayıoğlu <[email protected]>
Reviewed-By: Mattias Buelens <[email protected]>
Reviewed-By: Robert Nagy <[email protected]>

[detached HEAD 6e6bcb4f2f] stream: speed up async iteration of Readable
Author: Matteo Collina <[email protected]>
Date: Sat Jul 11 23:41:55 2026 +0200
3 files changed, 307 insertions(+), 35 deletions(-)
Rebasing (3/8)
Rebasing (4/8)
Executing: git node land --amend --yes
--------------------------------- New Message ----------------------------------
fixup: address review comments

Signed-off-by: Matteo Collina <[email protected]>
PR-URL: #64447
Reviewed-By: Gürgün Dayıoğlu <[email protected]>
Reviewed-By: Mattias Buelens <[email protected]>
Reviewed-By: Robert Nagy <[email protected]>

[detached HEAD a9e0e0ad92] fixup: address review comments
Author: Matteo Collina <[email protected]>
Date: Mon Jul 13 10:00:44 2026 +0200
3 files changed, 73 insertions(+), 26 deletions(-)
Rebasing (5/8)
Rebasing (6/8)
Executing: git node land --amend --yes
--------------------------------- New Message ----------------------------------
fixup: remove [SymbolAsyncIterator]

Signed-off-by: Matteo Collina <[email protected]>
PR-URL: #64447
Reviewed-By: Gürgün Dayıoğlu <[email protected]>
Reviewed-By: Mattias Buelens <[email protected]>
Reviewed-By: Robert Nagy <[email protected]>

[detached HEAD 0ea470ea7b] fixup: remove [SymbolAsyncIterator]
Author: Matteo Collina <[email protected]>
Date: Wed Jul 15 17:34:22 2026 +0200
1 file changed, 1 insertion(+), 4 deletions(-)
Rebasing (7/8)
Rebasing (8/8)
Executing: git node land --amend --yes
--------------------------------- New Message ----------------------------------
stream: fix lint in readable async iterator

Signed-off-by: Matteo Collina <[email protected]>
PR-URL: #64447
Reviewed-By: Gürgün Dayıoğlu <[email protected]>
Reviewed-By: Mattias Buelens <[email protected]>
Reviewed-By: Robert Nagy <[email protected]>

[detached HEAD 0d2b7c1daf] stream: fix lint in readable async iterator
Author: Matteo Collina <[email protected]>
Date: Sun Aug 2 09:08:31 2026 +0000
1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/main.

ℹ Add commit-queue-squash label to land the PR as one commit, or commit-queue-rebase to land as separate commits.

https://github.com/nodejs/node/actions/runs/31846615170

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mcollina mcollina added commit-queue PRs queued for automated landing through the Commit Queue. commit-queue-squash PRs the Commit Queue should land as one squashed commit. and removed commit-queue-failed PRs whose Commit Queue landing failed and need manual intervention before retrying. labels Aug 14, 2026
@nodejs-github-bot
nodejs-github-bot merged commit 4551732 into nodejs:main Aug 14, 2026
86 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot

nodejs-github-bot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Landed in 4551732

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot nodejs-github-bot removed the commit-queue PRs queued for automated landing through the Commit Queue. label Aug 14, 2026
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
@mcollina @aduh95
stream: speed up async iteration of Readable
58dc209
Replace the async generator backing Symbol.asyncIterator with a
hand-rolled iterator. The generator machinery costs several extra
promise allocations and microtask hops per chunk: yield awaits the
yielded value and resolves the pending request through separate
promises. Buffered chunks are now delivered as an already-resolved
promise, one microtask sooner than before.

Thenable chunks are still awaited before delivery, requests received
while a next() is outstanding are queued, and return()/throw() before
the first next() complete the iterator without touching the stream.

The earlier delivery is observable by code racing an abort against
the first chunk. The flatMap AbortSignal test relied on such a race;
it is reworked to abort deterministically while two mappers are in
flight, asserting the concurrency limit, in-flight cancellation and
rejection, without depending on delivery timing or timers.

streams/readable-async-iterator.js sync='yes': +32.59% (***)
streams/readable-async-iterator.js sync='no': +9.84% (***)

Assisted-by: Claude Fable 5
Signed-off-by: Matteo Collina <[email protected]>
PR-URL: #64447
Reviewed-By: Gürgün Dayıoğlu <[email protected]>
Reviewed-By: Mattias Buelens <[email protected]>
Reviewed-By: Robert Nagy <[email protected]>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
@mcollina @aduh95
stream: speed up async iteration of Readable
5edffb5
Replace the async generator backing Symbol.asyncIterator with a
hand-rolled iterator. The generator machinery costs several extra
promise allocations and microtask hops per chunk: yield awaits the
yielded value and resolves the pending request through separate
promises. Buffered chunks are now delivered as an already-resolved
promise, one microtask sooner than before.

Thenable chunks are still awaited before delivery, requests received
while a next() is outstanding are queued, and return()/throw() before
the first next() complete the iterator without touching the stream.

The earlier delivery is observable by code racing an abort against
the first chunk. The flatMap AbortSignal test relied on such a race;
it is reworked to abort deterministically while two mappers are in
flight, asserting the concurrency limit, in-flight cancellation and
rejection, without depending on delivery timing or timers.

streams/readable-async-iterator.js sync='yes': +32.59% (***)
streams/readable-async-iterator.js sync='no': +9.84% (***)

Assisted-by: Claude Fable 5
Signed-off-by: Matteo Collina <[email protected]>
PR-URL: #64447
Reviewed-By: Gürgün Dayıoğlu <[email protected]>
Reviewed-By: Mattias Buelens <[email protected]>
Reviewed-By: Robert Nagy <[email protected]>
@github-actions github-actions Bot mentioned this pull request Aug 25, 2026
2026-08-26, Version 26.8.0 (Current) #65551
Merged
@everett1992 everett1992 mentioned this pull request Sep 10, 2026
stream: fix async iteration of undefined chunks #65969
Merged
everett1992 added a commit to everett1992/node that referenced this pull request Sep 10, 2026
@everett1992
stream: fix async iteration of undefined chunks
ebc4cde
The hand-rolled async iterator introduced in
nodejs#64447 unwraps thenable chunks before
delivering them, but guards the `then` access with `chunk !== null` while
the dereference itself requires `chunk != null`. An object mode stream
carrying an `undefined` chunk therefore threw

  TypeError: Cannot read properties of undefined (reading 'then')

Because the throw happens in a microtask rather than rejecting the
iterator's promise, it surfaces as an uncaught exception that terminates
the process instead of a catchable stream error.

`undefined` is a legal chunk value: doc/api/stream.md documents object
mode chunks as "any JavaScript value other than `null`" and reserves
`null` as the end-of-stream sentinel. `readable.push(undefined)` returns
true in object mode, so the stream accepts the value and then crashes on
the way out.

Read `then` with optional chaining at both sites so that `undefined` is
delivered as a value while `null` keeps signalling end-of-stream. `then`
is still read at most once, so a getter cannot observe a second access.

Refs: nodejs#64447
everett1992 added a commit to everett1992/node that referenced this pull request Sep 10, 2026
@everett1992
stream: fix async iteration of undefined chunks
ccff441
The hand-rolled async iterator introduced in
nodejs#64447 unwraps thenable chunks
before delivering them, but guards the `then` access with
`chunk !== null` while the dereference itself requires `chunk != null`.
An object mode stream carrying an `undefined` chunk therefore threw

  TypeError: Cannot read properties of undefined (reading 'then')

Because the throw happens in a microtask rather than rejecting the
iterator's promise, it surfaces as an uncaught exception that
terminates the process instead of a catchable stream error.

`undefined` is a legal chunk value: doc/api/stream.md documents object
mode chunks as "any JavaScript value other than `null`" and reserves
`null` as the end-of-stream sentinel. `readable.push(undefined)`
returns true in object mode, so the stream accepts the value and then
crashes on the way out.

Read `then` with optional chaining at both sites so that `undefined` is
delivered as a value while `null` keeps signalling end-of-stream.
`then` is still read at most once, so a getter cannot observe a second
access.

Refs: nodejs#64447
everett1992 added a commit to everett1992/node that referenced this pull request Sep 10, 2026
@everett1992
stream: fix async iteration of undefined chunks
4ce986f
The hand-rolled async iterator introduced in
nodejs#64447 unwraps thenable chunks
before delivering them, but guards the `then` access with
`chunk !== null` while the dereference itself requires `chunk != null`.
An object mode stream carrying an `undefined` chunk therefore threw

  TypeError: Cannot read properties of undefined (reading 'then')

Because the throw happens in a microtask rather than rejecting the
iterator's promise, it surfaces as an uncaught exception that
terminates the process instead of a catchable stream error.

`undefined` is a legal chunk value: doc/api/stream.md documents object
mode chunks as "any JavaScript value other than `null`" and reserves
`null` as the end-of-stream sentinel. `readable.push(undefined)`
returns true in object mode, so the stream accepts the value and then
crashes on the way out.

Read `then` with optional chaining at both sites so that `undefined` is
delivered as a value while `null` keeps signalling end-of-stream.
`then` is still read at most once, so a getter cannot observe a second
access.

Refs: nodejs#64447
Signed-off-by: Caleb ツ Everett <[email protected]>
everett1992 added a commit to everett1992/node that referenced this pull request Sep 10, 2026
@everett1992
stream: fix async iteration of undefined chunks
b970c5d
The hand-rolled async iterator introduced in
nodejs#64447 unwraps thenable chunks
before delivering them, but guards the `then` access with
`chunk !== null` while the dereference itself requires `chunk != null`.
An object mode stream carrying an `undefined` chunk therefore threw

  TypeError: Cannot read properties of undefined (reading 'then')

Because the throw happens in a microtask rather than rejecting the
iterator's promise, it surfaces as an uncaught exception that
terminates the process instead of a catchable stream error.

`undefined` is a legal chunk value: doc/api/stream.md documents object
mode chunks as "any JavaScript value other than `null`" and reserves
`null` as the end-of-stream sentinel. `readable.push(undefined)`
returns true in object mode, so the stream accepts the value and then
crashes on the way out.

Read `then` with optional chaining at both sites so that `undefined` is
delivered as a value while `null` keeps signalling end-of-stream.
`then` is still read at most once, so a getter cannot observe a second
access.

Refs: nodejs#64447
Assisted-by: a closed-source coding agent
Signed-off-by: Caleb ツ Everett <[email protected]>
nodejs-github-bot pushed a commit that referenced this pull request Sep 12, 2026
@everett1992
stream: fix async iteration of undefined chunks
1c179c9
The hand-rolled async iterator introduced in
#64447 unwraps thenable chunks
before delivering them, but guards the `then` access with
`chunk !== null` while the dereference itself requires `chunk != null`.
An object mode stream carrying an `undefined` chunk therefore threw

  TypeError: Cannot read properties of undefined (reading 'then')

Because the throw happens in a microtask rather than rejecting the
iterator's promise, it surfaces as an uncaught exception that
terminates the process instead of a catchable stream error.

`undefined` is a legal chunk value: doc/api/stream.md documents object
mode chunks as "any JavaScript value other than `null`" and reserves
`null` as the end-of-stream sentinel. `readable.push(undefined)`
returns true in object mode, so the stream accepts the value and then
crashes on the way out.

Read `then` with optional chaining at both sites so that `undefined` is
delivered as a value while `null` keeps signalling end-of-stream.
`then` is still read at most once, so a getter cannot observe a second
access.

Refs: #64447
Assisted-by: a closed-source coding agent
Signed-off-by: Caleb ツ Everett <[email protected]>
PR-URL: #65969
Reviewed-By: Xuguang Mei <[email protected]>
Reviewed-By: James M Snell <[email protected]>
aduh95 pushed a commit that referenced this pull request Sep 16, 2026
@everett1992 @aduh95
stream: fix async iteration of undefined chunks
04e5c28
The hand-rolled async iterator introduced in
#64447 unwraps thenable chunks
before delivering them, but guards the `then` access with
`chunk !== null` while the dereference itself requires `chunk != null`.
An object mode stream carrying an `undefined` chunk therefore threw

  TypeError: Cannot read properties of undefined (reading 'then')

Because the throw happens in a microtask rather than rejecting the
iterator's promise, it surfaces as an uncaught exception that
terminates the process instead of a catchable stream error.

`undefined` is a legal chunk value: doc/api/stream.md documents object
mode chunks as "any JavaScript value other than `null`" and reserves
`null` as the end-of-stream sentinel. `readable.push(undefined)`
returns true in object mode, so the stream accepts the value and then
crashes on the way out.

Read `then` with optional chaining at both sites so that `undefined` is
delivered as a value while `null` keeps signalling end-of-stream.
`then` is still read at most once, so a getter cannot observe a second
access.

Refs: #64447
Assisted-by: a closed-source coding agent
Signed-off-by: Caleb ツ Everett <[email protected]>
PR-URL: #65969
Reviewed-By: Xuguang Mei <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@jasnell jasnell jasnell left review comments
@Renegade334 Renegade334 Renegade334 left review comments
@aduh95 aduh95 aduh95 left review comments
@MattiasBuelens MattiasBuelens MattiasBuelens approved these changes
@ronag ronag ronag approved these changes
@gurgunday gurgunday gurgunday approved these changes
+1 more reviewer
@mertcanaltin mertcanaltin mertcanaltin approved these changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

author ready PRs with CI started, the required approvals, and no outstanding review comments. commit-queue-squash PRs the Commit Queue should land as one squashed commit. needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

9 participants

@mcollina @nodejs-github-bot @jasnell @MattiasBuelens @ronag @Renegade334 @aduh95 @mertcanaltin @gurgunday
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.