文章摘要
文章指出当前JavaScript的Web Streams API存在根本性的可用性和性能问题,这些问题源于十年前的设计决策,已无法适应当今开发者的编码方式。尽管该标准已被主流平台广泛采用,但作者认为仅靠渐进式改进难以解决这些深层问题,呼吁重新设计更优的流处理API。
文章总结
JavaScript 流式 API:现状与革新
当前 Web Streams API 的问题
Web Streams API 自 2014-2016 年设计以来,已成为浏览器和服务器端(如 Cloudflare Workers、Node.js 等)处理流数据的标准。然而,经过多年实践,其设计暴露出以下核心问题:
复杂性与易用性不足
- 基础操作(如读取流)需手动管理锁和读取器,代码冗长:
javascript const reader = stream.getReader(); try { while (true) { const { value, done } = await reader.read(); if (done) break; chunks.push(value); } } finally { reader.releaseLock(); } - 尽管支持
for await...of,但底层仍依赖复杂的读取器模型,且无法访问 BYOB 等高级功能。
- 基础操作(如读取流)需手动管理锁和读取器,代码冗长:
锁机制易出错
- 忘记调用
releaseLock()会导致流永久锁定,且错误难以调试。
- 忘记调用
BYOB(自带缓冲区)的复杂性与低效
- 需使用专用 API(如
ReadableStreamBYOBReader),且涉及缓冲区分离等晦涩概念,实际使用率低。
- 需使用专用 API(如
背压(Backpressure)机制缺陷
desiredSize仅为建议值,无法强制生产者减速,可能导致内存无限增长。tee()等操作会隐式缓冲数据,缺乏配置选项。
性能瓶颈
- 规范强制使用 Promise,在高频场景(如视频流)产生显著开销。例如:
- Node.js 中,Web Streams 的管道吞吐量比优化后的原生实现低 12 倍。
- 服务器端渲染(SSR)因大量短期对象分配导致 GC 压力激增。
- 规范强制使用 Promise,在高频场景(如视频流)产生显著开销。例如:
现实中的故障案例
- 未消费的响应体:忽略
fetch()返回的流体会导致连接泄漏。 tee()内存问题:分支读取速度不均时,内部缓冲无限制增长。- 转换流背压失效:
TransformStream在写入侧忽略背压信号,导致下游积压。
设计根源问题
- 历史局限:规范早于 JavaScript 的异步迭代(ES2018),未能利用现代语言特性。
- 过度抽象:锁、控制器等概念增加了实现复杂度,而跨运行时优化困难。
新流式 API 设计提案
核心原则
流即异步可迭代对象
- 直接使用
AsyncIterable<Uint8Array[]>,无需自定义类或锁管理。 - 示例:
javascript const chunks = []; for await (const chunk of stream) { chunks.push(chunk); }
- 直接使用
拉取式转换(Pull-through Transforms)
- 数据按需流动,停止迭代即停止处理,避免隐式缓冲。
显式背压策略
- 提供四种策略(严格拒绝/阻塞/丢弃旧数据/丢弃新数据),取代模糊的
desiredSize。
- 提供四种策略(严格拒绝/阻塞/丢弃旧数据/丢弃新数据),取代模糊的
批处理与同步优化
- 返回
Uint8Array[]减少 Promise 开销;支持纯同步路径提升 CPU 密集型任务性能。
- 返回
仅处理字节流
- 统一使用
Uint8Array,字符串自动编码,简化类型系统。
- 统一使用
性能优势
基准测试对比(Node.js): | 场景 | 新 API 吞吐量 | Web Streams 吞吐量 | 提升倍数 | |---------------------|---------------|--------------------|---------| | 小数据块(1KB×5000) | ~13 GB/s | ~4 GB/s | 3× | | 高频数据(64B×20000)| ~7.5 GB/s | ~280 MB/s | 25× | | 链式转换(3层) | ~275 GB/s | ~3 GB/s | 90× |
浏览器测试:Chrome 中异步迭代快 40-100 倍。
实现示例
- 创建流:
javascript const { writer, readable } = Stream.push(); await writer.write("Hello"); const text = await Stream.text(readable); // 自动解码 - 转换流:
javascript const toUpperCase = (chunks) => chunks?.map(c => new TextEncoder().encode( new TextDecoder().decode(c).toUpperCase() )); const output = Stream.pull(source, toUpperCase);
后续计划
- 开源参考实现:GitHub - new-streams
- 目标:引发社区讨论,推动流式 API 的现代化改进。
总结
Web Streams API 的设计受限于历史条件,而新提案通过简化抽象、拥抱现代 JavaScript 特性(如异步迭代)和严格背压控制,显著提升了性能与易用性。这一探索旨在为下一代流式处理奠定基础。
评论总结
总结评论内容:
对现有流标准的批评
- 认为底层技术栈不适合当前需求,导致过度抽象化
- 引用:"The entire stack we’re using right down to the hardware is not fit for purpose"
- 引用:"we’re burning our talent and money building these ever more brittle towering abstractions"
BYOB(自带缓冲区)读取的复杂性问题
- 认为BYOB实现复杂但性能关键,应简化
- 引用:"BYOB reads are so complex and such a pain in the neck"
- 引用:"A simpler, more ergonomic approach to buffer management would go a long way"
JavaScript语言本身的局限性
- 批评JavaScript不够理想,WebAssembly未达预期
- 引用:"We deserve a better language than JavaScript"
- 引用:"WebAssembly failed to keep some of its promises"
改进流API的提案
- 提出更灵活的流迭代器设计(支持同步/异步混合)
- 引用:"My way... the whole iteration can be sync making it possible to get and use the result in sync functions"
- 引用:"Stream iterators can help you manage concurrency, which is a huge thing that async iterators cannot do"
现有流实现的肯定
- 部分用户认可Node.js流的实用性
- 引用:"I like Node.JS streams... let it process GB's of data using streams"
- 引用:"we should just use streams, especially with how embedded they are in the
fetchproposals"
资源管理的改进建议
- 推荐使用
using/await using模式进行资源清理 - 引用:"I’m hoping it becomes more popular with libraries... works similarly to C#'s use of using"
- 推荐使用
关键分歧点:
- 流抽象的必要性:一方认为当前抽象过度(评论1),另一方认为应接受现有标准(评论10)
- API设计方向:同步/异步混合迭代器(评论6) vs 纯异步迭代器(评论11)
- 语言层面:部分用户呼吁替代JavaScript(评论5),其他用户专注于改进现有生态(评论9)
(注:所有评论均无评分数据,故未体现认可度差异)