denoland/deno · error · TypeError

This BYOB request has been invalidated

Error message

This BYOB request has been invalidated

What it means

respond() (or respondWithNewView()) was called on a ReadableStreamBYOBRequest whose internal controller reference is undefined — the request was invalidated. The controller issues a new BYOBRequest per pending pull-into and invalidates the old one whenever the pending read is fulfilled or shifted (e.g. by a controller.enqueue()), the stream closes, or the request is superseded. Responding on any stale request object throws.

Solutions

  1. Read controller.byobRequest fresh at the top of every pull() and respond at most once per non-null request.
  2. Never store the request beyond the current pull's lifetime.
  3. If events can arrive late, re-check controller.byobRequest before responding and drop responses for superseded requests.

Example fix

// before
class Source {
  pull(controller) {
    if (!this.byob) this.byob = controller.byobRequest; // cached once
    this.byob.respond(n); // later pull -> TypeError: invalidated
  }
}

// after
class Source {
  pull(controller) {
    const byob = controller.byobRequest; // fresh every pull
    if (byob === null) return;           // none pending
    byob.respond(n);
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// always re-derive; the old object is dead after any enqueue/respond/close
const byob = controller.byobRequest;
if (byob === null) return; // request invalidated or none pending
byob.respond(n);

Try / catch

try {
  byob.respond(n);
} catch (e) {
  if (e instanceof TypeError && e.message.includes('invalidated')) {
    return; // superseded request — ignore the late response
  }
  throw e;
}

Prevention

When it happens

Trigger: Caching controller.byobRequest in a variable (or on this) and reusing it across pulls; calling respond() twice on the same request; responding on a request that a parallel controller.enqueue() already satisfied; responding after close().

Common situations: Source adapters storing byobRequest on the instance for convenience; event-driven code where a late event responds on a superseded request; refactors from per-pull requests to a cached-request design.

Related errors


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

Appendix: source

Thrown at ext/web/06_streams.js:6726

    }
    this[_brand] = _brand;
  }

  respond(bytesWritten) {
    webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype);
    const prefix = "Failed to execute 'respond' on 'ReadableStreamBYOBRequest'";
    webidl.requiredArguments(arguments.length, 1, prefix);
    bytesWritten = webidl.converters["unsigned long long"](
      bytesWritten,
      prefix,
      "Argument 1",
      {
        enforceRange: true,
      },
    );

    if (this[_controller] === undefined) {
      throw new TypeError("This BYOB request has been invalidated");
    }

    let buffer, byteLength;
    if (isTypedArray(this[_view])) {
      buffer = TypedArrayPrototypeGetBuffer(this[_view]);
      byteLength = TypedArrayPrototypeGetByteLength(this[_view]);
    } else {
      buffer = DataViewPrototypeGetBuffer(this[_view]);
      byteLength = DataViewPrototypeGetByteLength(this[_view]);
    }
    if (isDetachedBuffer(buffer)) {
      throw new TypeError(
        "The BYOB request's buffer has been detached and so cannot be used as a response",
      );
    }
    assert(byteLength > 0);
    assert(getArrayBufferByteLength(buffer) > 0);
    readableByteStreamControllerRespond(this[_controller], bytesWritten);

View on GitHub (pinned to 9ad36f7a2c)