What problem are you trying to solve?
There is no ergonomic, standardized way to externally control a ReadableStream (i.e., enqueue, close, or error) with a safe interface.
Today:
- The only way is to keep a reference to
ReadableStreamDefaultController.
- Calling
controller.enqueue(), controller.close(), or controller.error() after the stream is closed/errored/canceled may throw.
- Developers sometimes implement ad-hoc helpers ("withResolvers"-style wrappers) to build "pushable streams", but this pattern is not standardized.
This makes event-based or imperative stream production awkward and error-prone.
What solutions exist today?
-
Manually handling ReadableStreamDefaultController
- Requires writing boilerplate.
- Unsafe: calling methods after close/error/cancel throws.
- Requires custom guarding logic.
-
Ad-hoc helper utilities
- Non-standard.
- Inconsistent behavior (especially around cancellation and error propagation).
-
Async generator wrappers
- Cannot expose a truly push-based API.
- Cannot support backpressure-compatible push-style enqueuing.
How would you solve it?
Introduce a resolver-style API, similar to Promise.withResolvers():
interface ReadableStream {
static withSafeResolvers<T = unknown>(): {
stream: ReadableStream<T>;
enqueue(chunk: T): void;
close(): void;
error(reason: unknown): void;
};
}
Key behavior
enqueue(chunk) — enqueues a chunk; ignored if closed/errored/canceled.
close() — closes the stream safely; ignored after finalization.
error(reason) — errors the stream; ignored after finalization.
- All operations after the stream is finalized (closed, errored, or canceled) are silently ignored.
This provides an ergonomic, safe way to create externally controlled pushable streams.
Anything else?
A reference implementation exists:
This proposal maintains compatibility with all existing stream semantics (including backpressure). The API surface is minimal and follows existing web-standard precedents such as Promise.withResolvers().
What problem are you trying to solve?
There is no ergonomic, standardized way to externally control a
ReadableStream(i.e., enqueue, close, or error) with a safe interface.Today:
ReadableStreamDefaultController.controller.enqueue(),controller.close(), orcontroller.error()after the stream is closed/errored/canceled may throw.This makes event-based or imperative stream production awkward and error-prone.
What solutions exist today?
Manually handling
ReadableStreamDefaultControllerAd-hoc helper utilities
Async generator wrappers
How would you solve it?
Introduce a resolver-style API, similar to
Promise.withResolvers():Key behavior
enqueue(chunk)— enqueues a chunk; ignored if closed/errored/canceled.close()— closes the stream safely; ignored after finalization.error(reason)— errors the stream; ignored after finalization.This provides an ergonomic, safe way to create externally controlled pushable streams.
Anything else?
A reference implementation exists:
This proposal maintains compatibility with all existing stream semantics (including backpressure). The API surface is minimal and follows existing web-standard precedents such as
Promise.withResolvers().