Обычный fetch работает так: браузер ждёт, пока сервер отправит весь ответ, и только потом отдаёт данные вашему коду. Для небольших JSON-ов это нормально. Но если ответ большой, или сервер генерирует его постепенно (как это делают языковые модели при стриминге), ждать полного ответа — плохая идея. Streams API позволяет читать ответ прямо по мере поступления.
В ReadableStream каждый ответ fetch имеет свойство body — это ReadableStream. Из него можно читать данные кусками (chunks) по мере их поступления:
const response = await fetch('/api/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(decoder.decode(value));
}
Метод reader.read() возвращает промис с объектом { done, value }. value — это Uint8Array (сырые байты), поэтому нужен TextDecoder для перевода в строку. Когда данные кончаются, done становится true.
Зачем это нужно
Самый популярный сценарий сегодня — стриминг ответа от LLM-сервисов. ChatGPT, Claude и другие модели отправляют текст постепенно, слово за словом. Это именно поток: сервер держит соединение открытым и шлёт данные по мере генерации.
Другие сценарии:
- загрузка больших файлов с отображением прогресса
- чтение CSV или JSON построчно без загрузки всего файла в память
- Server-Sent Events через
fetch
Отмена стриминга
Читатель потока можно отменить в любой момент через reader.cancel() или через AbortController:
const controller = new AbortController();
const response = await fetch('/api/stream', {
signal: controller.signal
});
const reader = response.body.getReader();
// Позже — отменяем поток
controller.abort();
Трансформация потока
Streams API позволяет создавать цепочки трансформаций через TransformStream. Например, декодировать байты в текст прямо в потоке:
const stream = response.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toUpperCase());
}
}));
Это мощный инструмент для обработки больших данных без загрузки всего в память.
ReadableStream и fetch().body поддерживаются во всех современных браузерах. TextDecoderStream и pipeThrough — тоже. В Node.js нативный fetch со стримингом доступен с версии 18.