{"record":{"id":"49b40fc44fc8d553","repo":"denoland/deno","slug":"body-already-consumed-49b40f","errorCode":null,"errorMessage":"Body already consumed.","messagePattern":"Body already consumed\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/fetch/22_body.js","lineNumber":414,"sourceCode":"    },\n    text: {\n      __proto__: null,\n      /** @returns {Promise<string>} */\n      value: function text() {\n        return consumeBody(this, \"text\");\n      },\n      writable: true,\n      configurable: true,\n      enumerable: true,\n    },\n    textStream: {\n      __proto__: null,\n      /** @returns {ReadableStream<string>} */\n      value: function textStream() {\n        webidl.assertBranded(this, prototype);\n        const inner = this[bodySymbol];\n        if (inner !== null && inner.unusable()) {\n          throw new TypeError(\"Body already consumed.\");\n        }\n        if (inner === null) {\n          // A null body yields an empty, already-closed stream. Per the spec\n          // this is returned as-is; no decoder is set up for it.\n          const emptyStream = new ReadableStream();\n          readableStreamClose(emptyStream);\n          return emptyStream;\n        }\n        return inner.stream.pipeThrough(new TextDecoderStream());\n      },\n      writable: true,\n      configurable: true,\n      enumerable: true,\n    },\n  };\n  return ObjectDefineProperties(prototype, mixin);\n}\n","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/fetch/22_body.js#L396-L432","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Choose one consumption strategy per body: either one-shot (text/json) or streaming (textStream/body), never both.","If you must re-stream, capture the original bytes first and construct a new Request/Response with them.","Clone the request before the first read if a second consumer might need the stream."],"exampleFix":"// before\nconst raw = await req.text();\nfor await (const chunk of req.textStream) { ... } // Body already consumed.\n\n// after\nfor await (const chunk of req.textStream) {\n  process(chunk);\n}\n// if you also need the full text, accumulate chunks inside the loop","handlingStrategy":"validation","validationCode":"const canStream = req.body !== null && !req.bodyUsed && !req.body?.locked;\nif (!canStream) throw new Error(\"body unavailable for streaming\");\nfor await (const chunk of req.textStream) { /* ... */ }","typeGuard":"function isBodyStreamable(r: Request | Response): boolean {\n  return !r.bodyUsed && (r.body === null || !r.body.locked);\n}","tryCatchPattern":null,"preventionTips":["Pick one read strategy (one-shot vs streaming) per request.","Check bodyUsed before accessing stream APIs.","Clone before reading if you might need the other representation."],"tags":["fetch","body","stream","validation"],"backgroundTag":"request-body-already-read","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}