Web Streams TransformStream 如何处理背压
前端处理大文件、网络响应或实时数据时,最容易出现的症状是生产端不断 enqueue,消费端却来不及读取,结果内存队列越来越长。TransformStream 的背压不是另加一个“限速开关”,而是由 readable 侧的队列状态影响 writable 侧是否继续接受写入,再沿着 pipeThrough 管道传回上游。
官方资料:https://developer.mozilla.org/en-US/docs/Web/API/TransformStream
- 自动管道优先用
readable.pipeThrough(transform).pipeTo(writable),让下游能力决定上游节奏。 controller.desiredSize观察 TransformStream 的 readable 队列,writer.desiredSize和writer.ready观察 writable 队列。- highWaterMark 只改变“何时施加压力”的阈值,不会让慢消费者凭空变快。
先画清 writable、transform 和 readable 的背压方向
TransformStream 可以看成一条中间管道:输入写入 writable,transform(chunk, controller)产生输出,消费者从 readable读取。下游读取速度变慢时,readable 内部队列接近高水位线,TransformStream 的 writable 写入会等待;如果它前面还有 ReadableStream,压力会继续向前传播。
这里有两个容易混淆的观察点。transform 回调里的 controller.desiredSize表示关联 readable 队列还希望接收多少大小;手动取得 writable writer 后,writer.desiredSize表示 writable 队列距离高水位线还有多少空间。数值小于或等于零时,应把它当作“继续写入会积压”的信号,而不是把它当作已经丢数据。

用 pipeThrough 建立自动背压管道
如果只是把数据从来源变换后交给另一个 WritableStream,优先让 Streams API 管理等待关系。pipeThrough()把 TransformStream 放进管道,pipeTo()负责连接最终写入端;当最终写入端返回一个尚未完成的写入 Promise,前面的读取就会自然放慢。
const upperCase = new TransformStream({
transform(chunk, controller) {
// 只转换当前分块;enqueue 会把结果放入 readable 侧队列。
controller.enqueue(String(chunk).toUpperCase());
}
});
const slowSink = new WritableStream({
async write(chunk) {
// 用延迟模拟下游处理较慢;真实项目中这里可能是文件或网络写入。
await new Promise(resolve => setTimeout(resolve, 20));
console.log(chunk);
}
});
// pipeThrough 保留 TransformStream 的两端,pipeTo 连接最终消费者。
await source.pipeThrough(upperCase).pipeTo(slowSink);
这段示例的关键不是延迟值,而是没有在生产循环里无条件把所有 chunk 先放进数组。若 source 是自定义 ReadableStream,还应在 pull() 中依据 controller 的 desiredSize 决定是否继续准备数据。管道完成时用 pipeTo() 返回的 Promise 作为整体成功信号。
手动写入时等待 writer.ready
上传分片、逐块解码或需要自己掌控生命周期时,可以直接获取 TransformStream 的 writable writer。此时不要只打印一次 desiredSize就继续写;更可靠的做法是写入一块后,在队列进入压力区时等待 writer.ready。该 Promise 会在队列恢复到可接受状态时解决。

const writer = transform.writable.getWriter();
try {
for (const chunk of chunks) {
// write 返回的 Promise 表示这一块已交给 writable 侧处理。
await writer.write(chunk);
// desiredSize 非正时说明队列有压力,等待 ready 再继续。
if (writer.desiredSize
writer.ready是等待“背压解除”的信号,不是所有业务处理完成的信号;真正的整体完成仍要等待 readable 被消费完或外层管道 Promise 结束。若 write、ready 或 close 抛错,应把它当作错误/关闭路径处理,不要用 while 循环忙等。
检查高水位线、读取端和验证信号
背压看起来“不生效”时,按下面三层检查,通常比单纯调大 highWaterMark 更快定位:
| 检查点 | 看什么 | 结论 |
|---|---|---|
| 队列阈值 | readableStrategy.highWaterMark、writableStrategy.highWaterMark和 size() | 阈值改变只影响触发压力的时机,size 不匹配会让“一个 chunk”并不等于一字节。 |
| 消费路径 | 最终 writable 的 write()是否真的等待 I/O | 如果下游立即 resolve,背压很快解除;若业务另有数组缓存,内存仍会增长。 |
| 状态信号 | desiredSize、writer.ready、pipeTo()的 resolve/reject | 区分暂时积压、正常关闭和错误终止,不能把负数当成丢包证据。 |
最后做一次反向验证:让消费端人为变慢,观察生产端是否在若干块后出现等待;再恢复消费速度,确认 writer.ready能够继续推进。这个验证只说明管道的节奏受下游影响,并不代表所有数据源都能被暂停,已经进入外部 SDK 或不可控回调的缓存仍需单独治理。
相关问题
把 highWaterMark 调大是不是就能解决内存上涨?
不能。它只扩大队列偏好的容量,可能延后背压触发;如果消费者持续慢,积压仍会增长,应先确认消费路径和 chunk 的 size 计算。
controller.desiredSize 和 writer.desiredSize 能互相替代吗?
不能。前者观察 transform 输出的 readable 队列,后者观察手动写入的 writable 队列。自动 pipe 链路通常无需手工读取它们,只有自定义 source 或直接 writer 写入时才需要据此做额外控制。
记住一条判断线:让数据经过可传播背压的 Streams 管道,优先等待 Promise;只有在自己生产或写入时,才用 desiredSize 判断压力、用 ready 等待恢复,并在 close、abort、cancel 的生命周期上做完整收尾。
Go goroutine 退出前为什么要通知所有等待者
- 上一篇
- Go goroutine 退出前为什么要通知所有等待者
- 下一篇
- 墨刀AI做产品周报PPT怎么减少返工?先固定数据口径和页面骨架
-
- 文章 · 前端 | 2小时前 |
- JavaScript structuredClone 复制 Map 和 Set 时如何保留类型
- 153浏览 收藏
-
- 文章 · 前端 | 3小时前 | javascript · fetch · 前端异步 · JavaScript Fetch 事件监听 AbortController AbortSignal
- JavaScript AbortController 如何取消 fetch 和事件监听
- 150浏览 收藏
-
- 文章 · 前端 | 6小时前 | 布局 · css · 前端性能 · CSS content-visibility contain-intrinsic-size 布局跳动
- CSS contain-intrinsic-size 如何减少内容跳动
- 136浏览 收藏
-
- 文章 · 前端 | 8小时前 | css · 前端性能 · content-visibility · 滚动布局 ·
- CSS content-visibility 使用后滚动位置为什么会跳动
- 325浏览 收藏
-
- 文章 · 前端 | 10小时前 | 前端 · javascript · Web API · SHA-256 Blob.slice Web Crypto subtle.digest
- Web Crypto subtle.digest 处理大文件时如何分块计算
- 475浏览 收藏
-
- 文章 · 前端 | 12小时前 | 前端 · javascript · IndexedDB ·
- IndexedDB 事务异步回调结束后为什么自动提交
- 419浏览 收藏
-
- 文章 · 前端 | 13小时前 | BroadcastChannel · 前端通信 · JavaScript BroadcastChannel 多标签同步
- BroadcastChannel 多标签同步时如何忽略自己发出的消息
- 209浏览 收藏
-
- 文章 · 前端 | 15小时前 | 前端性能 · 流式读取 · Web Streams · TEE ReadableStream Web Streams backpressure
- Web Streams tee 分流后一个消费者变慢会发生什么
- 485浏览 收藏
-
- 文章 · 前端 | 16小时前 | javascript · Web API · 前端路由 · URLPattern · URL路由 URLPattern 可选语言前缀 资源ID
- URLPattern 如何同时匹配可选语言前缀和资源 ID
- 173浏览 收藏
-
- 文章 · 前端 | 17小时前 |
- Popover API 点击外部自动关闭时如何保留表单状态
- 220浏览 收藏
-
- 文章 · 前端 | 18小时前 | html · 前端 · javascript · close事件 HTML dialog showModal returnValue
- HTML dialog 模态关闭后如何读取 returnValue
- 339浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 31次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 132次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 68次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 24次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 14次使用
-
- JavaScript函数定义及示例详解
- 2025-05-11 502浏览
-
- 智能体安全引领产业升级——国内AI安全产品市场深度分析
- 2026-08-21 501浏览
-
- CSS变量简化按钮悬停效果技巧
- 2026-05-31 501浏览
-
- JavaScript符号类型详解与应用
- 2026-05-31 501浏览
-
- HTML剪贴板复制粘贴怎么用
- 2026-05-26 501浏览

