denoland/deno · error · TypeError
Cannot enqueue chunk when underlying stream is not readable
Error message
Cannot enqueue chunk when underlying stream is not readable
What it means
Thrown by ReadableByteStreamController.enqueue(chunk) when the stream's state is not "readable" (ext/web/06_streams.js) — the stream was canceled by its reader, errored via controller.error(), or already closed. Chunks cannot be delivered to a non-readable stream, so enqueue is rejected.
Source
Thrown at ext/web/06_streams.js:6890
prefix,
arg1,
);
}
if (getArrayBufferByteLength(buffer) === 0) {
throw webidl.makeException(
TypeError,
"Buffer length must be non-zero",
prefix,
arg1,
);
}
if (this[_closeRequested] === true) {
throw new TypeError(
"Cannot enqueue chunk after a close has been requested",
);
}
if (this[_stream][_state] !== "readable") {
throw new TypeError(
"Cannot enqueue chunk when underlying stream is not readable",
);
}
return readableByteStreamControllerEnqueue(this, chunk);
}
/**
* @param {any=} e
* @returns {void}
*/
error(e = undefined) {
webidl.assertBranded(this, ReadableByteStreamControllerPrototype);
if (e !== undefined) {
e = webidl.converters.any(e);
}
readableByteStreamControllerError(this, e);
}
View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Implement cancel(reason) in the underlying source and stop enqueueing once it fires.
- Before enqueueing, check controller.desiredSize — null means errored; treat 0-with-no-progress plus a done flag as ended; on any terminal signal stop producing.
- Route all enqueue calls through one helper that first checks your producer's done/canceled state.
- Clear timers/callbacks in the cancel path so no enqueue can race cancellation.
Example fix
// before
new ReadableStream({
start(c) { setInterval(() => c.enqueue(poll()), 10); }, // keeps firing after cancel
});
// after
new ReadableStream({
start(c) {
this.t = setInterval(() => { if (!this.done) c.enqueue(poll()); }, 10);
},
cancel() { this.done = true; clearInterval(this.t); },
}); Defensive patterns
Strategy: validation
Validate before calling
// Stop when the consumer canceled: desiredSize is null once errored,
// and cancellation closes the stream. Combine with your own done flag.
function safeEnqueue(controller, chunk, done) {
if (done || controller.desiredSize === null) {
stopProducing(); // cancel timers, unsubscribe events
return;
}
controller.enqueue(chunk);
} Try / catch
try {
controller.enqueue(chunk);
} catch (err) {
if (err instanceof TypeError && /not readable/.test(err.message)) stopProducing();
else throw err;
} Prevention
- Implement cancel(reason) in every ReadableStream underlying source you write.
- Shut down timers/event subscriptions in the cancel path so no enqueue races it.
- Treat reader cancellation as a hard stop for the producer.
When it happens
Trigger: The consumer calls stream.cancel() (e.g. aborted fetch, client disconnect) while the producer keeps enqueueing; enqueueing after controller.error(e) already errored the stream; enqueueing after the stream closed.
Common situations: Long-running producers that ignore cancellation (no cancel() implementation in the underlying source); server handlers whose client aborted mid-stream; error paths that error the stream but leave timers/callbacks pushing data.
Related errors
- Closed already requested.
- ReadableByteStreamController's stream is not in a readable s
- Cannot enqueue chunk after a close has been requested
- Chunk's buffer is detached and so cannot be enqueued
- "autoAllocateChunkSize" must be greater than 0
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/34035578e9677ca3.
Report an issue: GitHub.