使用 async/await 处理异步流错误的关键是不能依赖顶层 try/catch,而必须在每次 await reader.read() 或 for await...of 迭代中主动捕获错误,并配合 abortcontroller 控制生命周期、监听 error 事件以覆盖所有错误场景。

使用 async/await 处理异步流(如 ReadableStream、AsyncIterator 或 Node.js 的 stream.Readable)中的错误,关键在于:**不能只靠顶层 try/catch 捕获所有异常,必须在流消费过程中主动监听或 await 每个异步操作,并对每个可能失败的步骤做错误处理。**
理解异步流的错误触发时机
异步流的错误可能发生在三个地方:
-
流创建阶段:例如
fetch()返回的Response.body流,若网络失败,getReader()可能不报错,但首次reader.read()会 reject -
读取阶段:每次
await reader.read()都是一个独立 Promise,任一调用可能被 reject(如连接中断、解码失败) - 流结束前的隐式错误:比如可取消流被 abort,或底层资源(如文件句柄)提前关闭
用 try/catch 包裹每次 await read()
这是最直接可靠的方式——把每次读取视为一个独立异步操作,逐个捕获:
async function consumeStream(reader) {
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 处理 chunk: value
processChunk(value);
}
} catch (err) {
// 这里能捕获 read() 抛出的任何错误(网络中断、类型错误、abort 等)
console.error('流读取失败:', err);
throw err; // 或按需处理:重试、降级、上报
} finally {
reader.releaseLock(); // 确保释放锁
}
}
配合 AbortController 主动控制流生命周期
避免“错误发生时已无法响应”的情况,把 abort 信号传入流操作:
- 创建
AbortController,将signal传给read()(部分环境支持,如 Chrome 120+) - 更通用做法:在
catch中检查err.name === 'AbortError',区分主动取消和意外错误 - 手动调用
controller.abort()触发清理逻辑(如关闭 socket、取消 pending 请求)
处理可迭代的异步流(for await...of)
对于实现了 [Symbol.asyncIterator] 的流(如 Response.body 在现代浏览器中),for await 更简洁,但错误仍需在循环内捕获:
async function handleResponseStream(response) {
if (!response.body) throw new Error('No body');
try {
for await (const chunk of response.body) {
// chunk 是 Uint8Array,可直接处理
handleChunk(chunk);
}
} catch (err) {
// 注意:这里捕获的是迭代器内部 read() 的 reject,不是 response 初始化错误
if (err.name === 'TypeError' && err.message.includes('terminated')) {
console.warn('连接被服务端终止');
} else {
console.error('流迭代异常:', err);
}
}
}
⚠️ 注意:for await...of 无法捕获流创建阶段的错误(如 fetch 失败),那部分需在外层 try/catch。
Node.js 中 readable stream 的 async iterator 写法
Node.js v10+ 原生支持 for await,但默认不自动处理 'error' 事件 —— 必须确保流处于 flowing 模式且监听了 error:
import { createReadStream } from 'fs';
async function readLargeFile(path) {
const stream = createReadStream(path, { encoding: 'utf8' });
// 必须监听 error,否则未捕获的 error 会抛到 uncaughtException
stream.on('error', (err) => {
console.error('文件流错误:', err);
});
try {
for await (const chunk of stream) {
console.log(`收到 ${chunk.length} 字符`);
}
} catch (err) {
// 这里也会捕获部分错误(如解码失败),但 event error 优先走 on('error')
console.error('迭代过程出错:', err);
}
}
不复杂但容易忽略:异步流的错误是分散在每一次 read() 中的,没有“全局 try”这回事。把每次 await 当作一次独立调用去保护,再结合信号控制和事件监听,就能稳住整个流程。











