denoland/deno · error · TypeError

Body already consumed.

Error message

Body already consumed.

What it means

Thrown by the body mixin's textStream() accessor (ext/fetch/22_body.js) when the inner body exists and inner.unusable() is true — the same consumed/locked condition as consume(), but surfaced through the property-like textStream API. Note the trailing period in the message; it comes from the newer property-based body accessors.

Solutions

  1. Choose one consumption strategy per body: either one-shot (text/json) or streaming (textStream/body), never both.
  2. If you must re-stream, capture the original bytes first and construct a new Request/Response with them.
  3. Clone the request before the first read if a second consumer might need the stream.

Example fix

// before
const raw = await req.text();
for await (const chunk of req.textStream) { ... } // Body already consumed.

// after
for await (const chunk of req.textStream) {
  process(chunk);
}
// if you also need the full text, accumulate chunks inside the loop
Defensive patterns

Strategy: validation

Validate before calling

const canStream = req.body !== null && !req.bodyUsed && !req.body?.locked;
if (!canStream) throw new Error("body unavailable for streaming");
for await (const chunk of req.textStream) { /* ... */ }

Type guard

function isBodyStreamable(r: Request | Response): boolean {
  return !r.bodyUsed && (r.body === null || !r.body.locked);
}

Prevention

When it happens

Trigger: Accessing `request.textStream` (or the property path that calls it) after the body was already read via text()/json()/arrayBuffer() or after res.body was locked by a reader.

Common situations: Switching mid-handler from one-shot reads to streaming; frameworks that call textStream internally after middleware consumed the body; debugging code that reads the body first and later pipes textStream.

Related errors


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

Appendix: source

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

    },
    text: {
      __proto__: null,
      /** @returns {Promise<string>} */
      value: function text() {
        return consumeBody(this, "text");
      },
      writable: true,
      configurable: true,
      enumerable: true,
    },
    textStream: {
      __proto__: null,
      /** @returns {ReadableStream<string>} */
      value: function textStream() {
        webidl.assertBranded(this, prototype);
        const inner = this[bodySymbol];
        if (inner !== null && inner.unusable()) {
          throw new TypeError("Body already consumed.");
        }
        if (inner === null) {
          // A null body yields an empty, already-closed stream. Per the spec
          // this is returned as-is; no decoder is set up for it.
          const emptyStream = new ReadableStream();
          readableStreamClose(emptyStream);
          return emptyStream;
        }
        return inner.stream.pipeThrough(new TextDecoderStream());
      },
      writable: true,
      configurable: true,
      enumerable: true,
    },
  };
  return ObjectDefineProperties(prototype, mixin);
}

View on GitHub (pinned to 9ad36f7a2c)