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

  1. Call close() exactly once per controller; track it with a boolean (e.g. let closed = false; if (!closed) { closed = true; controller.close(); }).
  2. Restructure so only the EOF path of the source closes the controller; cleanup paths should only run if close was not yet requested.
  3. 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

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


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/524c1b54bd98dfeb. Report an issue: GitHub.