denoland/deno · error · TypeError
Readable stream is unavailable
Error message
Readable stream is unavailable
What it means
TransformStreamDefaultController.enqueue() was called while the TransformStream's readable side can no longer accept chunks — it is closed or errored, so readableStreamDefaultControllerCanCloseOrEnqueue is false. Typical causes: the consumer cancelled the readable side, a previous transform already called controller.close() or controller.error(), or enqueue runs after the stream finished.
Source
Thrown at ext/web/06_streams.js:4848
controller[_transformAlgorithm] = undefined;
controller[_flushAlgorithm] = undefined;
controller[_cancelAlgorithm] = undefined;
}
/**
* @template O
* @param {TransformStreamDefaultController<O>} controller
* @param {O} chunk
*/
function transformStreamDefaultControllerEnqueue(controller, chunk) {
const stream = controller[_stream];
const readableController = stream[_readable][_controller];
if (
readableStreamDefaultControllerCanCloseOrEnqueue(
/** @type {ReadableStreamDefaultController<O>} */ readableController,
) === false
) {
throw new TypeError("Readable stream is unavailable");
}
try {
readableStreamDefaultControllerEnqueue(
/** @type {ReadableStreamDefaultController<O>} */ readableController,
chunk,
);
} catch (e) {
transformStreamErrorWritableAndUnblockWrite(stream, e);
throw stream[_readable][_storedError];
}
const backpressure = readableStreamDefaultcontrollerHasBackpressure(
/** @type {ReadableStreamDefaultController<O>} */ readableController,
);
if (backpressure !== stream[_backpressure]) {
assert(backpressure === true);
transformStreamSetBackpressure(stream, true);
}
}View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Check controller.desiredSize — it is null exactly when the readable side is closed or errored; skip the enqueue then.
- Do not enqueue after close() or error(); make flush() a no-op once the readable side is gone.
- Treat downstream cancellation as a stop signal: abort outstanding work instead of continuing to transform.
Example fix
// before
transform(chunk, controller) {
controller.close();
controller.enqueue(chunk); // readable closed -> TypeError
}
// after
transform(chunk, controller) {
if (controller.desiredSize === null) return; // readable gone
controller.enqueue(chunk);
} Defensive patterns
Strategy: validation
Validate before calling
// inside transform()/flush()
if (controller.desiredSize === null) {
return; // readable side closed or errored — do not enqueue
}
controller.enqueue(out); Try / catch
try {
controller.enqueue(out);
} catch (e) {
if (e instanceof TypeError && e.message === 'Readable stream is unavailable') {
return; // consumer is gone: stop transforming, let cancellation propagate
}
throw e;
} Prevention
- desiredSize === null is the canonical 'cannot enqueue' signal on transform controllers.
- Never enqueue after close() or error().
- Treat downstream cancellation as a stop signal for upstream work.
When it happens
Trigger: controller.enqueue(chunk) after controller.close() in the same transformer; enqueueing after controller.error(...) or after an earlier enqueue threw; async work started in transform() resolving after the consumer cancelled the readable side.
Common situations: Transformers emitting a final chunk from flush() after closing early; fire-and-forget promises inside transform(); multi-stage pipelines where a downstream stage errored or the client disconnected.
Related errors
- Delay must be >= 0: received ${delay}
- Delay cannot be greater than 30 days: received ${delay}
- Failed to enqueue value
- Chunk's buffer is detached and so cannot be enqueued
- The BYOB request's buffer has been detached and so cannot be
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/06f2750df8f4cbdd.
Report an issue: GitHub.