先记住这个答案
Writable.end 表示不再提交新数据,并让已排队写入按正常流程完成,成功后进入可写侧 finish。destroy 则让流进入销毁流程,适用于错误或取消,不能保证尚未完成的数据全部交付;传入错误通常会沿流的错误路径报告,并按实现关闭相关资源。destroyed 为 true 不等于异步资源清理已经全部完成,也不会撤销已经写入文件或发送到远端的数据。应区分正常完成、异常中断和外部业务回滚,按各自协议处理。
- end 用于正常结束生产并完成已有写入
- destroy 不保证把剩余缓冲全部交付
- 销毁流不会自动回滚已经发生的外部副作用
正常结束需要给已接收的数据完成机会
对仍可写的流调用 end,可以附带最后一块数据,并告诉实现不再有后续输入。随后应等待 finish 或合适的完成工具,处理期间发生的错误;调用 end 返回本身不是所有写入已经完成的同步证明。
end 之后继续 write 属于错误使用,不能把 end 当成临时刷新。若只是想把一批小写入向底层交付而之后还要继续生产,应考虑相应批处理和背压机制,而不是反复结束同一个流。
销毁意味着未完成部分不再有成功保证
错误或取消时调用 destroy,流会停止正常使用并执行相关销毁逻辑,未处理缓冲可能被放弃。已经开始的底层异步操作或已经发送的数据不一定能撤回,因此消费者可能已收到一个前缀。
这对文件导出和网络传输很重要:失败后可能存在部分产物。业务层需要临时文件、最终改名、校验或事务等合适策略,不能因为流对象被销毁就认为磁盘或远端状态自动恢复到开始之前。
标志、事件与自定义资源清理要分别验证
destroyed 标志表明已调用销毁流程,但底层异步清理可能仍在进行。close 表示相应关闭过程的事件,具体流还可能配置不发出它;传入错误后通常需要 error 处理,不能只关注一个布尔属性。
实现自定义流时,外部计时器、请求或句柄应在合适的销毁实现中释放,并正确完成回调。不要直接手动伪造 error 或 finish 事件来代替生命周期,也不要假设 Node 能知道业务代码在流外启动了哪些任务。
容易答错的地方
- 用 destroy 作为可靠刷新剩余数据的方法
- 销毁适合中断,不保证缓冲全部成功交付。需要正常完成时应结束生产并等待成功结果;若因错误必须中止,则把部分数据作为失败产物处理,不能同时承诺所有剩余数据已写完。
- 看见 destroyed 就立即宣布资源清理完毕
- 该标志与异步底层关闭不是同一个时刻。应等待符合目标流契约的完成或关闭证据,并核对自定义外部资源;对未知实现不能只凭一个属性就释放所有业务状态。
面试官还会怎么问?
destroy(err) 一定会出现 finish 吗?
不能依赖正常 finish。销毁可能在写入完成前中止流,应该走错误或取消处理;如果业务需要区分成功与中断,应观察相应完成工具的结果,而不是只等成功事件。
双工流调用 end 会让可读侧也立刻结束吗?
不一定,end 主要结束可写侧,另一侧是否继续取决于流类型、实现和半关闭等设置。网络双工与转换流的具体语义不同,应分别确认读写完成条件,不能一概当作整个对象立即关闭。
pipeline 失败后还要自己处理什么?
pipeline 会管理参与流的错误和销毁,但流外的临时文件、业务事务、任务记录和其他请求仍由应用负责。应明确所有权和补偿策略,避免把数据链的清理能力误认为整个业务的自动回滚。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。