denoland/deno · error · TypeError

Body already consumed

Error message

Body already consumed

What it means

InnerBody.consume() in ext/fetch/22_body.js backs res.text(), res.json(), res.arrayBuffer(), res.blob(), res.bytes() and res.formData(). It throws TypeError "Body already consumed" when unusable(): the underlying ReadableStream is locked or disturbed (a reader was acquired, or it was piped/read), or a static body is already marked consumed. A body can be consumed exactly once.

Source

Thrown at ext/fetch/22_body.js:189

   */
  consumed() {
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      return isReadableStreamDisturbed(this.streamOrStatic);
    }
    return this.streamOrStatic.consumed;
  }

  /**
   * https://fetch.spec.whatwg.org/#concept-body-consume-body
   * @returns {Promise<Uint8Array>}
   */
  consume() {
    if (this.unusable()) throw new TypeError("Body already consumed");
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      readableStreamThrowIfErrored(this.stream);
      return PromisePrototypeCatch(
        readableStreamCollectIntoUint8Array(this.stream),
        (e) => {
          if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, e)) {
            // TODO(kt3k): We probably like to pass e as `cause` if BadResource supports it.
            throw new e.constructor(
              "Cannot read body as underlying resource unavailable",
            );
          }
          throw e;
        },

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Check res.bodyUsed before any read and parse once, storing the result
  2. Call res.clone() before the first read when two consumers need the body
  3. In middleware, buffer the bytes: const buf = await res.clone().arrayBuffer() and pass the original on, or construct a fresh Response from the buffered bytes

Example fix

// before
const a = await res.json();
const b = await res.json(); // TypeError: Body already consumed

// after
const data = await res.json();
const a = data;
const b = data;
// or: const [a, b] = await Promise.all([res.clone().json(), res.json()]);
Defensive patterns

Strategy: validation

Validate before calling

if (res.bodyUsed) {
  throw new Error("body already read - reuse the cached parsed value");
}
const data = await res.json();

Type guard

function hasUnreadBody(res: Response | Request): boolean {
  return !res.bodyUsed && !(res.body?.locked ?? false);
}

Try / catch

try { return await res.json(); } catch (e) {
  if (e instanceof TypeError && e.message === "Body already consumed") {
    throw new Error("duplicate body read; cache the first parse or clone() before reading");
  }
  throw e;
}

Prevention

When it happens

Trigger: await res.text() twice on the same response; res.json() after const reader = res.body.getReader(); calling res.formData() after piping res.body elsewhere.

Common situations: Logging middleware that reads the body for logging and then passing the same response to business code; retry logic that re-parses a response; caching layers that hold the Response object instead of its parsed content.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/abe3287dd6ef37fc. Report an issue: GitHub.