当前位置:首页 > 文章列表 > 文章 > 前端 > Web Streams TransformStream 如何处理背压

Web Streams TransformStream 如何处理背压

来源:17golang原创 2026-09-15 08:08:53 0浏览 收藏

前端处理大文件、网络响应或实时数据时,最容易出现的症状是生产端不断 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.desiredSizewriter.ready观察 writable 队列。
  • highWaterMark 只改变“何时施加压力”的阈值,不会让慢消费者凭空变快。

先画清 writable、transform 和 readable 的背压方向

TransformStream 可以看成一条中间管道:输入写入 writabletransform(chunk, controller)产生输出,消费者从 readable读取。下游读取速度变慢时,readable 内部队列接近高水位线,TransformStream 的 writable 写入会等待;如果它前面还有 ReadableStream,压力会继续向前传播。

这里有两个容易混淆的观察点。transform 回调里的 controller.desiredSize表示关联 readable 队列还希望接收多少大小;手动取得 writable writer 后,writer.desiredSize表示 writable 队列距离高水位线还有多少空间。数值小于或等于零时,应把它当作“继续写入会积压”的信号,而不是把它当作已经丢数据。

TransformStream 中 writable、transform、readable 与下游消费队列的静态关系示意图
图1:背压结构示意图。重点看“输出队列”和“下游消费”两个分组,压力会从 readable 侧反向影响 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 会在队列恢复到可接受状态时解决。

手动写入 TransformStream 时通过 writer desiredSize 和 ready 控制生产的静态关系示意图
图2:手动写入关系示意图。写入者只在 writable 队列允许时继续,ready 解决后再生产下一批数据。
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.highWaterMarkwritableStrategy.highWaterMarksize()阈值改变只影响触发压力的时机,size 不匹配会让“一个 chunk”并不等于一字节。
消费路径最终 writable 的 write()是否真的等待 I/O如果下游立即 resolve,背压很快解除;若业务另有数组缓存,内存仍会增长。
状态信号desiredSizewriter.readypipeTo()的 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 的生命周期上做完整收尾。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go goroutine 退出前为什么要通知所有等待者Go goroutine 退出前为什么要通知所有等待者
上一篇
Go goroutine 退出前为什么要通知所有等待者
墨刀AI做产品周报PPT怎么减少返工?先固定数据口径和页面骨架
下一篇
墨刀AI做产品周报PPT怎么减少返工?先固定数据口径和页面骨架
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    31次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    132次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    68次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    24次使用
  • CMMLU中文大模型评估基准:功能、使用教程与应用场景解析
    CMMLU
    深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
    14次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码