denoland/deno · error · TypeError
Body already consumed.
Error message
Body already consumed.
What it means
The body-mixin textStream accessor (ext/fetch/22_body.js) returns a decoded ReadableStream<string> for a body. If the inner body is non-null and unusable() (stream locked or already read), it throws TypeError "Body already consumed." - note the trailing period, which distinguishes it from the consume() variant. A null body is fine: it returns an empty, already-closed stream.
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 89f33cbef2)
Solutions
- Decide one consumption mode per body: stream it OR buffer it, never both
- Check res.bodyUsed before switching to the text stream
- If both are needed, tee/clone the source before the first read
Example fix
// before const reader = res.body.getReader(); const first = await reader.read(); // later: streaming text access on the same body -> TypeError // after const [a, b] = res.body.tee(); const first = await a.getReader().read(); // b remains usable for the text-stream path
Defensive patterns
Strategy: validation
Validate before calling
if (res.bodyUsed || res.body?.locked) {
throw new Error("body already consumed - cannot open text stream");
}
const stream = res.textStream ?? streamBodyAsText(res); // only on an unread body Type guard
function hasUnreadBody(res: Response | Request): boolean {
return !res.bodyUsed && !(res.body?.locked ?? false);
} Try / catch
try { return getTextStream(res); } catch (e) {
if (e instanceof TypeError && e.message.startsWith("Body already consumed.")) {
return ReadableStream.from([previouslyBufferedText]); // fall back to buffered copy
}
throw e;
} Prevention
- Pick one consumption mode per body up front: streaming or buffered
- tee()/clone() before the first read if two paths may consume it
- Check bodyUsed before any conditional fallback to streaming
When it happens
Trigger: Accessing the text stream of a Request/Response after await res.text() or after acquiring a reader on res.body; piping the body first and then requesting the text stream.
Common situations: Streaming handlers that conditionally fall back to full-text processing; SSE/websocket-ish code that sometimes swaps between streaming and buffered reads on the same body.
Related errors
- Body already consumed
- ReadableStream is locked or disturbed
- Request with GET/HEAD method cannot have body
- Input request's body is unusable
- Body is unusable
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/49b40fc44fc8d553.
Report an issue: GitHub.