fetch icon indicating copy to clipboard operation
fetch copied to clipboard

Convenience method to stream a response as text

Open foolip opened this issue 2 months ago • 9 comments

What problem are you trying to solve?

response.body is a stream of Uint8Array but often you want to deal with text. response.text() decodes the response body as text and returns it, but all at once, not streaming.

What solutions exist today?

let response = await fetch('/');
let decoder = new TextDecoderStream();
response.body.pipeThrough(decoder);

How would you solve it?

A textStream() helper that gives you a stream of decoded text chunks instead.

Anything else?

This is mainly motivated by https://github.com/whatwg/html/issues/11669, where a ReadableStream will needed. If it's as easy to get a string stream as a byte stream, then we could probably make those APIs only accept strings. (My current thinking was to bake text decoding into them to reduce boilerplate, but @noamr suggested a new method instead.)

foolip avatar Oct 02 '25 10:10 foolip

If this is done it should also be added to Blob too for consistency. https://w3c.github.io/FileAPI/#blob-section

lukewarlow avatar Oct 02 '25 12:10 lukewarlow

https://github.com/whatwg/fetch/pull/1862 is what it might look like. It amounts to this JS:

Response.prototype.textStream = function() {
  if (!this.body) {
    throw new TypeError("body is null");
  }
  return this.body.pipeThrough(new TextDecoderStream());
};

foolip avatar Oct 02 '25 12:10 foolip

@lukewarlow yes, that would make sense. I suppose for a very large blob this could be useful to not require a huge string allocation.

foolip avatar Oct 02 '25 12:10 foolip

I was trying to determine if in the future we might want this for any of the other consumption methods, but it seems unlikely, although I suppose there is talk of streaming JSON every now and then.

Agreed with @lukewarlow on Blob. I don't think this addition makes sense for PushMessageData, which wasn't mentioned thus far, especially given the push for Declarative Web Push which is purely JSON based.

I'd love to know what @bakkot makes of this.

annevk avatar Oct 02 '25 13:10 annevk

Sounds good to me, this is a pretty common use case in my experience, maybe getting more so with chatbots and similar streaming their responses. An additional reason not yet mentioned is that people who are unaware of TextDecoderStream may be tempted to do decoding one chunk at a time without a stateful TextDecoder, which will break if the end of a chunk happens to fall within a multi-byte character.

I agree it's unlikely we'll want a streaming version of .json. I could theoretically see value in a streaming .formData for the service worker use case - page submits a form with a file, service worker wants to modify some field but doesn't want to have to block on reading the whole file into memory. But that would require a bunch of other infrastructure and in any case is not ruled out by adding textStream if that use case becomes important someday.

I note that TextDecoder and TextDecoderStream support many more encodings than utf-8. I would think this should as well, given that the platform already supports those, but @annevk has objected to adding support in a similar API on the grounds that you should just fix legacy content. (In my experience web developers are very rarely in a position to fix legacy content.)

bakkot avatar Oct 02 '25 13:10 bakkot

It's a fair point, but I've yet to see actual complaints about enforcing UTF-8 in new APIs. And I suspect it does move the needle, even if only a little bit.

annevk avatar Oct 02 '25 14:10 annevk

I was trying to determine if in the future we might want this for any of the other consumption methods

I had the same question, The only thing I could see is a blobStream() that just turns each Uint8Array chunk into a Blob chunk, but that doesn't seem useful at all. Streaming JSON would quite handy, but would require a whole parsing spec, it's definitely not a "convenience method".

foolip avatar Oct 02 '25 14:10 foolip

On the encoding, matching text() means forcing UTF-8. That being said, a mode that respects the encoding in Content-Type seems like it would be less surprising than ignoring it. At this point such a mode (or an explicit encoding) would have to be an extra argument, which we should then give to both text() and textStream().

I don't have a strong view, but will note that because it's quite easy to work around with .pipeThrough(new TextDecoderStream("big5")) I think we probably wouldn't hear demands for this even if it was a nuisance that many people are working around.

foolip avatar Oct 02 '25 15:10 foolip

It would require a bit more code if they wanted to account for arbitrary Content-Type and the like. It also doesn't seem unreasonable to see if existing content is working around this or if libraries are; that would be necessary before we invest in a non-UTF-8-compatible solution in my opinion. (Which indeed can come later as we'd want to share it with text() anyway.)

annevk avatar Oct 02 '25 15:10 annevk