延迟收集异常是为保障流式输出连续性而将异常捕获推迟至流结束后的解耦策略,因HTTP连接提交后无法更改状态码,需通过补发error事件而非中断流来响应失败。

这种“延迟处理”不是把异常拖着不处理,而是把异常捕获动作推迟到流式管道自然结束之后再集中响应——它本质是一种解耦异常捕获时机与业务执行流程的设计策略,常见于需要保障流式输出连续性、避免中途中断的场景(如 SSE 接口、大模型流式生成、实时日志推送等)。
为什么需要延迟收集异常?
流式响应的核心约束是:一旦开始发送数据块(如 data: ...),HTTP 连接就进入“已提交”状态,无法再更改状态码或响应头。此时若中途抛出异常,传统 500 或 400 响应已不可行。
- 直接中断流会破坏客户端解析逻辑(例如前端 expect 一个完整的 SSE 流)
- 立即返回错误体可能与已发出的
add事件冲突,造成 UI 渲染错乱 - 部分异常需依赖完整上下文判断(如“生成中途 token 超限”,需等最后一块输出才确认)
典型实现结构
关键在于将异常暂存为状态变量,而非立即抛出:
- 定义一个可变容器(如 Go 中的
err atomic.Value,Java 中的AtomicReference<exception></exception>) - 在流式处理器内部所有可能出错的位置(模型调用、序列化、网络写入)用
defer或try-catch捕获,并存入该容器 - 主循环结束后检查该容器:若非空,补发
event: error+ 错误详情,再以data: [DONE]终止流
与常规全局异常处理的区别
它不替代 @ControllerAdvice 或 ExceptionFilter,而是与其协作:
- 全局异常处理器 仍负责拦截未被捕获的、导致请求线程崩溃的致命异常(如 NPE、OOM)
- 延迟收集模式 专注处理业务层可控的、预期内的失败路径(如模型返回空结果、权限校验失败但允许继续流式渲染)
- 二者共存时,通常约定:只有延迟收集器未接管的异常,才交由全局处理器降级为
500并关闭连接
实际注意事项
该模式有效,但需警惕副作用:
- 延迟时间不可控:若流式过程本身阻塞(如等待下游服务超时),异常响应也会被拖长
- 内存泄漏风险:长时间运行的流若持续积累异常对象,需配合超时清理
- 客户端必须兼容“先有正常数据、后有 error 事件”的语义,不能假设
error一定出现在开头

















