你以为 fetch 拿到的 body 只能靠 getReader 一点点读?今天 for await…of 把这件事彻底原生化了

写过 AI 流式响应的前端都踩过这个坑——fetch 拿到的 body 是一个 ReadableStream,以前想读它只能先 getReader(),然后 while(true) 反复调 read(),每次还要手动判断 done、releaseLock(),一不小心就把流锁死了。今天 for await…of 直接把这件事原生化了。

为什么这事值得关注

ReadableStream 是浏览器处理大文件、AI 流式响应、WebSocket 数据的核心接口。过去六年里,它一直是 async iterable 的,但各大浏览器到今年九月才把它正式接进 for await…of 循环——这意味着你终于可以用两行代码读任何流了。

来看对比。以前的写法:

const response = await fetch('/api/large-data');
const reader = response.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  process(value); // value 是 Uint8Array
}
reader.releaseLock();

今天的写法:

const response = await fetch('/api/large-data');
for await (const chunk of response.body) {
  process(chunk); // chunk 直接是 Uint8Array
}

没有任何样板代码,没有锁管理,没有 done 判断。Safari 27(2026-09-14)、Chrome 124、Firefox 110 全部支持,2026 年 9 月正式进 Baseline。

这在三个场景里影响最大:

AI 流式响应。Response.body 现在直接是 async iterable,AI 输出一个字你就处理一个字,不需要 getReader + while 循环:

const res = await fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ message: '写一个快速排序' })
});
for await (const chunk of res.body) {
  // 每个 chunk 是 AI 返回的文本片段
  text += new TextDecoder().decode(chunk);
  updateUI(text);
}

大文件分片下载。用管道操作符组合 transform stream,内存占用始终固定:

const res = await fetch('/large-file.zip');
const stream = res.body.pipeThrough(new DecompressionStream('gzip'));
for await (const chunk of stream) {
  writeToDisk(chunk);
}

WebSocket 消息流。告别手动管理 WebSocket 消息队列:

const ws = new WebSocket('wss://stream.example.com');
for await (const message of ws.messages) {
  handle(JSON.parse(message));
}

要注意的是 for await…of 会自动调用 return() 清理资源,所以 break 或异常退出时流锁会自动释放,不必担心泄漏。语法上也支持 async generator,可以一边读一边 yield 处理后的数据。

下一步:翻一下你的项目里有没有 getReader + while(true) 的组合,能换的就换了。浏览器帮你管锁,你管业务就行。

评论区

0 条评论

登录后可评论。

阿速·性能优化 437 阅读