← textStream() for response/request/blob
v151 · fetch · member reference
Response.textStream()
Reads a Response body as a ReadableStream of UTF-8-decoded strings — the streaming counterpart of response.text(), defined on the Body mixin in the Fetch Standard.
Syntax
// WHATWG Fetch — Body mixin (normative IDL)
ReadableStream textStream();
// Call form
const stream = response.textStream();
Chromium's binding: [RuntimeEnabled=TextStreamMethod, CallWith=ScriptState, RaisesException] ReadableStream textStream(); in body.idl.
Inputs
The method takes no parameters. Its only input is the receiver's own state: the Response object's body (null or a body with a ReadableStream) and whether that body is already disturbed or locked. There are no options, no encoding argument — decoding is always UTF-8.
Outputs
Returns a ReadableStream whose chunks are strings — the body's bytes run through a TextDecoderStream set up with UTF-8, “regardless of the presence or the value of a Content-Type header and regardless of the presence or the value of a charset parameter”. If the body's byte length is not a multiple of the encoding's unit size, the decoder completes any trailing sequence at end-of-stream.
Null body: if the response's body is null, the method returns a new ReadableStream that is already closed (it yields no chunks and is not an error). Null-body calls are not single-use: every call creates a distinct closed stream object, and bodyUsed remains false throughout, because the unusable/disturbed contract only exists for non-null bodies.
Errors
TypeError — unusable receiver: if the response is unusable — its body is non-null and that body's stream is already disturbed or locked (for example after a prior text(), textStream(), or a locked body reader) — the method throws a TypeError synchronously. This is the only exception in the method steps, mirrored by the binding's RaisesException.
Malformed bytes are not exceptions: the decoder runs in the Encoding Standard's default (non-fatal) error mode — TextDecoderOptions.fatal defaults to false — so invalid UTF-8 sequences decode to U+FFFD replacement characters in the output chunks rather than throwing or erroring the stream.
Context
Receiver: any Response instance — one resolved from fetch(), synthesized with new Response(...), or produced by a service worker / cache.
Exposure: Response is exposed in both Window and Worker contexts ([Exposed=(Window,Worker)] in Chromium's response.idl; the WPT suite runs with global=window,worker). No secure-context requirement and no permission prompt: the binding carries no [SecureContext] extended attribute and the method reads bytes the page already holds.
Availability: Chrome 151 per the milestone listing and BCD, gated at build time by the TextStreamMethod runtime feature (status stable at trunk).
Lifecycle
Immediate consumption — non-null bodies only: for a response with a body, calling textStream() starts the read at once — the WPT contract asserts bodyUsed becomes true synchronously, before any chunk is consumed (a non-null but empty body behaves the same way and then yields zero chunks).
Single use — non-null bodies only: while the returned stream is being read (and after it closes), the body's stream is disturbed, so the response is unusable — any further body method (text(), json(), a second textStream()) throws the TypeError above. Clone the response first (response.clone()) if two consumers are needed.
Null bodies are repeatable: with no body there is nothing to disturb — the WPT null-body test asserts bodyUsed stays false after repeated calls and that each call returns a different, independently closed stream object.
Stream lifetime: the returned stream is the body's stream piped through the decoder, so stream state (locked while a reader is attached, closed at end-of-body) follows ordinary pipe semantics.
Source: WPT textstream.any.js — bodyUsed and single-use contracts; Fetch Standard — textStream() method stepsExamples
// Stream a large text response as decoded chunks (never buffering it whole).
const response = await fetch("/large-log.txt");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const reader = response.textStream().getReader();
let total = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
total += value.length; // value is a string, UTF-8 decoded
}
console.log(`read ${total} characters`);
Source: WPT textstream.any.js — reader-loop shape; whatwg/fetch PR #1862
Compatibility
| Engine / runtime | Support | Notes |
|---|---|---|
| Chrome | 151 | Matches the Chrome 151 milestone listing (Enabled by default); Chrome for Android and WebView (Android + iOS) are BCD mirrors |
| Edge | mirror | BCD mirrors Chrome's data |
| Firefox | Not supported | BCD version_added: false (Firefox for Android mirrors); vendor signal recorded on the ChromeStatus entry: No signal |
| Safari | Not supported | BCD version_added: false (Safari on iOS mirrors); the ChromeStatus entry records a Positive signal — a recorded signal, not an official WebKit standards position (a feature this small has none) |
| Node.js | 26.5.0 | BCD records version_added: 26.5.0 |
| Deno | Unknown | No BCD entry as of 2026-07-26 |
Support rows are from BCD only. Separately, vendor signals come from the ChromeStatus API feature record (checked 2026-07-26): Firefox “No signal”, Safari “Positive”, web developers “No signals”. No Baseline entry: the webstatus.dev query returns no feature (checked 2026-07-26).
Source: BCD api/Response.json; ChromeStatus API feature record; webstatus.dev querySecurity and privacy
No new authority: the method needs no permission, no user gesture, and no secure context beyond what producing the Response already required — it only re-reads bytes the page already possesses (the same trust boundary as text() or arrayBuffer()).
Deterministic decoding: the decoder ignores Content-Type and charset and always uses UTF-8, so a server cannot change the decoding contract per response; malformed input becomes U+FFFD replacement text rather than an exception channel.
Disclosure/storage: nothing is persisted, transmitted, or shared across origins by this method itself; stream lifetime is bounded by the response's own lifetime.
Source: Fetch Standard — textStream() method steps; Encoding Standard — TextDecoderStream error mode