调用 ReadableStream.getReader() 会独占锁住流,同一时间仅允许一个活跃 reader,重复调用抛出 TypeError;锁通过 [[locked]] 内部槽控制,禁止再次 getReader()、pipeTo()、pipeThrough() 及 BYOB 模式读取;锁确保读取顺序性,不阻塞数据流入;开启底层读取需调用 reader.read(),返回 Promise 并触发 pull() 或返回缓存 chunk;锁需显式释放:读完自动解锁、cancel() 主动终止或 finally 中调用 cancel()/releaseLock();未读未取消将导致流永久锁定。

调用 ReadableStream.getReader() 会立即获取一个 ReadableStreamDefaultReader 实例,并**独占性地锁住该流**——这意味着同一时间只能有一个活跃的 reader,后续再调用 getReader() 会直接抛出 TypeError(提示“stream is locked”)。
锁住流的本质与影响
锁是流对象内部的一个状态标记([[locked]] 内部槽),一旦设为 true,以下操作将被禁止:
- 再次调用
getReader() - 调用
pipeTo()、pipeThrough() - 调用
getReader({ mode: 'byob' })(即使模式不同也不行)
锁的存在是为了避免多个消费者竞争读取,保证数据消费的确定性和顺序性。它不阻塞底层源(如 fetch() 响应体),数据仍会持续流入队列,只是读取控制权交给了当前 reader。
开启底层读取器的实际步骤
所谓“开启底层读取器”,本质是通过 reader 的 read() 方法触发流的内部读取机制。每次调用 read() 会:
- 若内部队列有数据,立即返回已缓存的 chunk(
{ value, done: false }) - 若队列为空且流未关闭/取消,向底层源请求新数据(例如触发
pull()回调) - 若流已关闭,返回
{ value: undefined, done: true }
注意:read() 返回的是 Promise,需用 await 或 .then() 处理;不主动调用 read(),reader 虽存在但底层不会启动拉取逻辑。
释放锁与正确清理
锁不会自动释放,必须显式终止 reader:
-
读完并关闭:持续
read()直到done: true,然后 reader 自动解锁 -
主动取消:调用
reader.cancel(reason),流进入closed状态并解锁 -
异常中断:reader 被垃圾回收前未完成或取消,流可能卡在
readable状态,但锁仍存在——此时应确保finally中调用cancel()或releaseLock()(仅限已读完)
⚠️ 错误做法:创建 reader 后不读也不 cancel,会导致流永久锁定,后续任何消费方式都失效。
常见场景示例
比如处理 fetch 响应流:
const response = await fetch('/data');
const reader = response.body.getReader(); // 此刻流已被锁
let result = new Uint8Array(0);
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 拼接 chunk
const newResult = new Uint8Array(result.length + value.length);
newResult.set(result);
newResult.set(value, result.length);
result = newResult;
}
// reader 自动解锁,result 即完整响应体
如果中途想中止,改用 reader.cancel('abort') 即可提前解锁并终止底层传输。

















