Skip to content

feat: improved stream handling#1928

Open
veeceey wants to merge 2 commits intokoajs:masterfrom
veeceey:fix/issue-1864-improved-stream-handling
Open

feat: improved stream handling#1928
veeceey wants to merge 2 commits intokoajs:masterfrom
veeceey:fix/issue-1864-improved-stream-handling

Conversation

@veeceey
Copy link

@veeceey veeceey commented Feb 20, 2026

Summary

Addresses #1864 - Improved stream handling in Koa.

  • Async iterable/generator support: ctx.body now accepts async iterables (e.g. async function*) as response bodies, converting them to Node.js readable streams via Stream.Readable.from(). This is useful for streaming responses from async data sources without manually creating streams.
  • Better stream pipeline error handling: ERR_STREAM_PREMATURE_CLOSE errors (caused by client disconnects) are now filtered out in the stream.pipeline callback, preventing spurious error events on the application when clients simply close their connections.
  • Body setter support for async iterables: The response body setter properly handles async iterables by setting the default content type to octet-stream, stripping Content-Length (since the length is unknown ahead of time), and cleaning up any previous stream body.

Changes

  • lib/is-stream.js: Added isAsyncIterable() helper that detects async iterables while excluding types already handled (Node.js streams, ReadableStream, Blob, Response)
  • lib/application.js: Added async iterable detection in respond(), filtered ERR_STREAM_PREMATURE_CLOSE in pipeline error callback
  • lib/response.js: Added async iterable handling in the body setter
  • __tests__/application/respond.test.js: Added 9 new tests covering async generator responses, async iterable responses, buffer yields, content type handling, error propagation, and premature close filtering
  • __tests__/response/body.test.js: Added 4 new tests for body setter behavior with async iterables

Test plan

  • All 432 tests pass (419 existing + 13 new)
  • Standard linter passes on all modified source files
  • Verify async generator responses stream correctly with ctx.body = asyncGenerator()
  • Verify client disconnects no longer emit ERR_STREAM_PREMATURE_CLOSE errors
  • Verify existing stream, ReadableStream, Blob, and Response body types still work correctly

🤖 Generated with Claude Code

…error handling

Add support for async iterables (async generators) as response bodies,
filter ERR_STREAM_PREMATURE_CLOSE errors from client disconnects in the
stream pipeline, and add comprehensive tests for the new functionality.

Closes koajs#1864

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.90%. Comparing base (fecd464) to head (8ada204).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1928   +/-   ##
=======================================
  Coverage   99.90%   99.90%           
=======================================
  Files           9        9           
  Lines        2084     2098   +14     
=======================================
+ Hits         2082     2096   +14     
  Misses          2        2           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 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.

lib/is-stream.js Outdated
* @param {*} obj
* @return {boolean}
*/
const isAsyncIterable = (obj) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is duplicative logic here. Claude, in this session, is having the isSteam function do the same calculations as its new isAsyncIterable on line 34. AKA, you could remove some lines of this function.

I think this function can be removed, and
typeof obj[Symbol.asyncIterator] === 'function' could be the if statement logic on line 230 of lib/response.js...?

The isAsyncIterable helper had redundant type exclusion checks since
streams, ReadableStream, Blob, and Response are already handled by
preceding branches in both the body setter and respond function.
Inline the Symbol.asyncIterator check directly where needed.
@veeceey
Copy link
Author

veeceey commented Feb 20, 2026

Good call, yeah that function was doing unnecessary work since all those types (Stream, ReadableStream, Blob, Response) are already handled by the preceding branches. Removed isAsyncIterable and inlined the Symbol.asyncIterator check directly. Thanks for the suggestion!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants