Skip to content

Streams、背压与增量读取:练习答案 ​

答案对应正文的四个实验。每题都要同时记录读取到的 chunk、消费速度和结束原因;只看到最终文本不能证明流被正确关闭。

ReadableStream reader ​

js
const source = new ReadableStream({
  start(controller) {
    controller.enqueue(new TextEncoder().encode('A'))
    controller.enqueue(new TextEncoder().encode('B'))
    controller.close()
  }
})
const reader = source.getReader()
const decoder = new TextDecoder()
let text = ''
while (true) {
  const { value, done } = await reader.read()
  if (done) break
  text += decoder.decode(value, { stream: true })
}
text += decoder.decode()
console.log(text) // AB

done 为 true 才表示生产者关闭;一次 read() 返回的 chunk 不等于一个业务消息。若中途 reader.cancel('route-left'),后续读取应结束且 source 的 cancel 能观察到原因。

背压与队列 ​

让生产者每 10ms 入队、消费者每 100ms 消费,并在 pull() 中打印 controller.desiredSize。当队列接近 highWaterMark 时停止继续生成;若无视它,内存会随输入速率增长。答案应给出“生产暂停—消费—恢复”的日志顺序,而不是只写“有背压”。

TransformStream 管道 ​

js
const upper = new TransformStream({ transform(chunk, controller) {
  controller.enqueue(chunk.toUpperCase())
}})
const writer = upper.writable.getWriter()
const output = upper.readable.getReader()
await writer.write('a')
await writer.write('b')
await writer.close()
console.log((await output.read()).value) // A
console.log((await output.read()).value) // B
console.log((await output.read()).done) // true

关闭 writable 才会让 readable 完成;只调用 writer.releaseLock() 不会结束管道。变换函数抛错时,readable 应拒绝,调用方要在 catch/finally 中释放锁。

流式 UI 与不完整数据 ​

增量 JSON 不能按 chunk 直接 JSON.parse。先把文本追加到缓冲区,按换行或协议分隔符切帧;最后一个没有分隔符的残片要在 done 时报告为截断,而不是静默丢弃。UI 更新应在每帧成功解析后进行,取消时显示“已停止”,不要显示成服务器 500。

用心记录每一步成长