在JavaScript运行时环境中,数据并不总是以完整文件或完整字符串的形式一次性到达。网络响应、文件读写以及音视频处理等场景都会产生连续不断的信息片段。Stream API提供了一套标准化接口,让开发者能够以分块方式消费或生成这些数据,而不必把全部内容暂存到内存里。理解这套机制对于构建高性能前端与Node.js应用都十分关键。

可读流的基本结构与手动读取方式
可读流(ReadableStream)是Stream API中最常被接触到的部分。当使用fetch函数请求一个资源时,返回的Response对象上有一个名为body的属性,它就是一个可读流。与传统ajax把响应体整段存入responseText不同,可读流允许我们按自己的节奏去拿数据。每一个可读流内部都维护着一个队列,数据生产者往队列里塞块,消费者通过读取器把块取出来。
要手动读取,需要调用可读流的getReader方法,它会返回一个读取器对象。读取器提供了read函数,该函数返回一个Promise,解析值中包含一个done布尔量与value数据块。当done为true时说明流已经结束。下面这段代码展示了如何在浏览器里逐块读取文本并显示长度:
async function readStreamManually(url) {
const response = await fetch(url);
const reader = response.body.getReader();
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
received += value.length;
console.log('当前收到字节数:' + received);
}
console.log('流读取完毕');
}
这种写法虽然直观,但需要自己维护循环与状态。它的优势在于可以精确控制每一次读取的时机,比如在做断点续传或限速下载时非常有用。缺点则是代码相对冗长,且容易在异常分支中忘记释放读取器导致资源泄漏。实际项目中应当把reader.releaseLock纳入finally处理。
利用管道与转换流处理数据
如果不需要手动逐块处理,而是希望把可读流直接对接到某个目的地,可以使用pipeTo方法。比如把网络响应体直接写入到文件系统(在支持File System Access API的环境下),或传递给一个可写流做压缩。管道机制会自动调度背压,当消费者处理慢时,生产者会暂停入队,从而避免内存膨胀。
转换流(TransformStream)则位于可读与可写之间,能够对流经的数据做实时修改。例如把接收到的Uint8Array文本块累积起来按行分割,或者做增量解压。下面的示例构建一个将字节流转换为文本行流的转换流:
const textDecoder = new TextDecoder();
let buffer = '';
const lineTransform = new TransformStream({
transform(chunk, controller) {
buffer += textDecoder.decode(chunk, { stream: true });
const lines = buffer.split('n');
buffer = lines.pop();
for (const line of lines) {
controller.enqueue(line);
}
},
flush(controller) {
if (buffer) {
controller.enqueue(buffer);
}
}
});
async function readLines(url) {
const response = await fetch(url);
const lineStream = response.body.pipeThrough(lineTransform);
const reader = lineStream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log('一行内容:' + value);
}
}
上面的代码把原始的字节可读流通过pipeThrough接入转换流,得到的是按行分隔的数据流。这样做既保持了低内存占用,又让业务逻辑只需关心完整的行。转换流内部的状态管理由transform与flush两个回调承担,比在手动循环里维护buffer要清晰很多。需要注意的是,TextDecoder在stream模式下会缓存不完整的多字节字符,确保中文等UTF-8内容不会被截断。
Node.js环境下的流与Web Stream差异
在Node.js中,传统上使用的是内置的stream模块,它提供了Readable、Writable、Duplex等类,基于事件发射器模式,通过data、end事件来推送数据。而现代Node.js从v16左右开始也逐步实现了Web Stream API,使得前后端可以使用同一套概念。两者最大的差异在于背压表达方式:Node旧流依靠pause与resume方法,Web流依靠管道内部的Promise协作。
如果你的代码运行在Edge Runtime或现代浏览器,优先使用标准的ReadableStream与pipeTo,可以减少环境适配成本。下面演示在Node.js中用Web Stream读取本地文件并上传:
import { open } from 'node:fs/promises';
import { Readable } from 'node:stream/web';
async function fileToUpload(path, uploadUrl) {
const file = await open(path, 'r');
const { size } = await file.stat();
let offset = 0;
const readable = new Readable({
async pull(controller) {
const buf = new Uint8Array(64 * 1024);
const { bytesRead } = await file.read(buf, 0, buf.length, offset);
offset += bytesRead;
if (bytesRead === 0) {
controller.close();
await file.close();
} else {
controller.enqueue(buf.subarray(0, bytesRead));
}
},
cancel() {
file.close();
}
});
await fetch(uploadUrl, { method: 'POST', body: readable });
}
这段代码在Node.js里用node:stream/web的Readable构造了一个Web标准可读流,并直接作为fetch的body。这样的写法在服务端无需引入额外的适配层,就能复用浏览器端积累的流处理经验。不过在老版本Node里,可能仍需用pipe方法对接旧式可写流,此时要注意错误事件监听,防止未捕获的stream错误让进程退出。总体来看,Stream API把数据流动抽象为可组合、可暂停、可变换的单元,是处理大规模或实时数据的基石。
Stream_APIJavaScript数据流读取修改时间:2026-08-16 05:50:33