denoland/deno · error · TypeError
Closed already requested.
Error message
Closed already requested.
What it means
Thrown by ReadableByteStreamController.close() when this[_closeRequested] is already true (ext/web/06_streams.js). Closing a byte stream is single-shot: the first close() sets the close-requested flag, and any second close() on the same controller is a state-machine violation and throws a TypeError, per the WHATWG Streams spec.
Source
Thrown at ext/web/06_streams.js:6835
}
/** @returns {ReadableStreamBYOBRequest | null} */
get byobRequest() {
webidl.assertBranded(this, ReadableByteStreamControllerPrototype);
return readableByteStreamControllerGetBYOBRequest(this);
}
/** @returns {number | null} */
get desiredSize() {
webidl.assertBranded(this, ReadableByteStreamControllerPrototype);
return readableByteStreamControllerGetDesiredSize(this);
}
/** @returns {void} */
close() {
webidl.assertBranded(this, ReadableByteStreamControllerPrototype);
if (this[_closeRequested] === true) {
throw new TypeError("Closed already requested.");
}
if (this[_stream][_state] !== "readable") {
throw new TypeError(
"ReadableByteStreamController's stream is not in a readable state",
);
}
readableByteStreamControllerClose(this);
}
/**
* @param {ArrayBufferView} chunk
* @returns {void}
*/
enqueue(chunk) {
webidl.assertBranded(this, ReadableByteStreamControllerPrototype);
const prefix =
"Failed to execute 'enqueue' on 'ReadableByteStreamController'";
webidl.requiredArguments(arguments.length, 1, prefix);View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Call close() exactly once per controller; track it with a boolean (e.g. let closed = false; if (!closed) { closed = true; controller.close(); }).
- Restructure so only the EOF path of the source closes the controller; cleanup paths should only run if close was not yet requested.
- Wrap close() in a guarded helper so all call sites share one state check.
Example fix
// before
try {
controller.enqueue(chunk);
controller.close();
} finally {
controller.close(); // second close -> TypeError
}
// after
let closed = false;
const closeOnce = () => { if (!closed) { closed = true; controller.close(); } };
try {
controller.enqueue(chunk);
closeOnce();
} finally {
closeOnce(); // no-op if already closed
} Defensive patterns
Strategy: validation
Validate before calling
// closeRequested is not directly observable — track it yourself.
let closeRequested = false;
function closeOnce(controller) {
if (closeRequested) return;
closeRequested = true;
controller.close();
} Try / catch
try {
controller.close();
} catch (err) {
if (err instanceof TypeError && err.message === 'Closed already requested.') return; // benign
throw err;
} Prevention
- Route every close through one guarded helper with a shared flag.
- Only the EOF path of your source should close; cleanup paths must not.
- Review try/finally blocks for a second, unconditional close().
When it happens
Trigger: Calling controller.close() twice from start()/pull() logic; a close() in the success path plus another close() in a finally block; two branches of a producer (EOF branch and error-cleanup branch) both closing.
Common situations: Producers with try/finally cleanup that also close on normal EOF; recursive or event-driven push code where multiple callbacks race to close; refactors that add a second close 'for safety'.
Related errors
- ReadableByteStreamController's stream is not in a readable s
- Cannot enqueue chunk after a close has been requested
- Cannot enqueue chunk when underlying stream is not readable
- The stream controller cannot close or enqueue
- Chunk's buffer is detached and so cannot be enqueued
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/524c1b54bd98dfeb.
Report an issue: GitHub.