WritableStream 构造后需显式获取 writer 并调用 write() 和 close();write() 仅接受 Uint8Array,字符串须用 TextEncoder.encode() 转换;响应流式 HTML 时 Content-Type 必须为 text/html;charset=utf-8 且不可设 Content-Length;流与 writer 生命周期须严格绑定在单个事件处理函数内。

WritableStream 构造后必须配对使用 writer.write() 和 writer.close()
直接 new 一个 WritableStream 不会自动开始接收数据,它只是个“空管道”。你得显式拿到 writer,再调用 write() 才真正把 chunk 推进去。漏掉 writer.close() 会导致流永远处于 “writable” 状态,下游(比如 Response 或 TransformStream)收不到结束信号,可能卡住或报 TypeError: stream is locked。
常见错误写法:
const stream = new WritableStream({ write(chunk) { console.log(chunk); } });
// ❌ 没有 writer,chunk 根本不会进来
正确做法:
const stream = new WritableStream({
write(chunk) { console.log('received:', chunk); }
});
const writer = stream.getWriter();
writer.write(new Uint8Array([1, 2, 3])); // ✅
writer.close(); // ✅ 必须调用
向 Response 返回流式 HTML 时,headers 必须含 Content-Type 且不能设 Content-Length
浏览器解析流式 HTML 依赖正确的 MIME 类型和 chunked 编码。如果 Content-Type 缺失或写成 text/html; charset=utf-8(带分号但没空格),某些环境(如 Safari)会拒绝渲染;若手动设置了 Content-Length,服务端无法预知总长度,会导致连接提前关闭或乱码。
-
Content-Type推荐写成text/html;charset=utf-8(无空格,分号紧连) - 绝对不要设置
Content-Length——WritableStream本身不提供长度信息 - 确保服务器(如 Cloudflare Workers、Deno HTTP Server)支持
Transfer-Encoding: chunked
write() 的 chunk 类型受限:只接受 Uint8Array,字符串需先 new TextEncoder().encode()
WritableStream 底层只认二进制数据。传字符串会直接抛 TypeError: Failed to execute 'write' on 'WritableStreamDefaultWriter': parameter 1 is not of type 'Uint8Array'。别想当然用 writer.write('hello')。
正确转换方式:
const encoder = new TextEncoder();
const writer = stream.getWriter();
writer.write(encoder.encode('<h1>Loading...</h1>')); // ✅
writer.write(encoder.encode('<p>Step 1 done</p>')); // ✅
writer.close();
注意:TextEncoder 默认用 UTF-8,无需额外指定;频繁写小字符串时,复用同一个 encoder 实例更高效。
在 Service Worker 或 Cloudflare Workers 中使用需注意流的生命周期与作用域
流对象不能跨事件循环持久化。比如在 fetch 事件里创建了 WritableStream,却试图在定时器或 fetch 响应后异步写入,大概率遇到 TypeError: writer is not writable —— 因为响应已发出或上下文已销毁。
安全做法是:所有 write() 和 close() 必须在同一个事件处理函数内完成,或明确绑定到一个未终止的可写流实例上:
addEventListener('fetch', (event) => {
event.respondWith((async () => {
const stream = new WritableStream({ write: renderChunk });
const writer = stream.getWriter();
await doAsyncWork(); // 如 fetch 多个 API
writer.write(encoder.encode('<div>done</div>'));
writer.close(); // ✅ 在同一 async 函数内闭环
return new Response(stream, { headers: { 'Content-Type': 'text/html;charset=utf-8' } });
})());
});
真正容易被忽略的是:流一旦 close 或 error,writer 就不可重用;出错后也不能靠 try/catch 恢复 writer,必须重建整个流。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











