Last active
March 1, 2026 18:38
-
-
Save bokwoon95/cbf4fedefc9e2043ca5a820270659f28 to your computer and use it in GitHub Desktop.
streamResponseLines is a generator function that streams over a response body (returned by fetch()) line by line. Use it like this: `for await (const line of streamResponseLines(response)) { ... }`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * streamResponseLines is a generator function that streams over a response | |
| * body (returned by fetch()) line by line. Use it like this: | |
| * `for await (const line of streamResponseLines(response)) { ... }`. | |
| * | |
| * @param {Response} response | |
| */ | |
| async function* streamResponseLines(response) { | |
| const reader = response.body.getReader(); | |
| // Assume the response body is in UTF-8 encoding. | |
| const textDecoder = new TextDecoder("utf-8"); | |
| let line = ""; | |
| let chunk = new Uint8Array(); | |
| // Read from the response body in chunks. | |
| for (let readResult = await reader.read(); !readResult.done; readResult = await reader.read()) { | |
| if (chunk.length > 0) { | |
| // We have a carryover chunk from a previous iteration. There are | |
| // guaranteed to be no newlines inside since the previous iteration's | |
| // chunk.indexOf(10) would have caught it. | |
| // | |
| // Stream option has to be true because we haven't encountered a | |
| // newline yet, more data may be decoded for the current line. | |
| line += textDecoder.decode(chunk, { stream: true }); | |
| } | |
| // Get the reader's current chunk. | |
| chunk = readResult.value; | |
| // Jump to each newline '\n' byte in the chunk. 10 is the ASCII/UTF-8 | |
| // decimal value of the '\n' byte. | |
| for (let index = chunk.indexOf(10); index >= 0; index = chunk.indexOf(10)) { | |
| // We found a newline, decode everything up to this index and consider | |
| // that as a complete line and yield it. | |
| line += textDecoder.decode(chunk.subarray(0, index)); | |
| yield line; | |
| // Reset the line. | |
| line = ""; | |
| // Shorten the chunk to exclude what we have already decoded. | |
| chunk = chunk.subarray(index + 1); | |
| } | |
| } | |
| // Flush any remainder bytes in the chunk. | |
| if (chunk.length > 0) { | |
| line += textDecoder.decode(chunk); | |
| yield line; | |
| } | |
| } | |
| const response = await fetch("https://example.org/some/api/call"); | |
| for await (const line of streamResponseLines(response)) { | |
| console.log(line); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment