---
title: "Node.js 流式导出中途失败：用 pipeline 传递错误并清理"
date: 2026-11-13
slug: node-stream-pipeline-failure
canonical: https://kernelranch.com/blog/2026/11/node-stream-pipeline-failure
markdown_url: https://kernelranch.com/blog/2026/11/node-stream-pipeline-failure.md
tags: ["nodejs", "streams", "export"]
---报表下载中途写入失败，如果调用方只等源流读完，就可能提前记录成功。用 Promise 版 pipeline 等整个链路结束，把写入结果作为发布条件，并清理失败后留下的半成品。

## 1. 模拟写入第三块时失败

保存为 stream_lab.mjs，运行 node stream_lab.mjs。Writable 用回调模拟慢写端，第二次运行在第三块报错。

```javascript
import assert from 'node:assert/strict';
import { Readable, Writable } from 'node:stream';
import { pipeline } from 'node:stream/promises';

async function runExport(failAt) {
  let accepted = 0;
  let finalCalled = false;
  const source = Readable.from(['one', 'two', 'three', 'four']);
  const sink = new Writable({
    highWaterMark: 3,
    write(chunk, encoding, callback) {
      setTimeout(() => {
        if (accepted + 1 === failAt) {
          callback(new Error('injected write failure'));
          return;
        }
        accepted++;
        callback();
      }, 5);
    },
    final(callback) {
      finalCalled = true;
      callback();
    },
  });
  if (failAt) {
    await assert.rejects(pipeline(source, sink), /injected write failure/);
    assert.equal(accepted, 2);
    assert.equal(finalCalled, false);
    assert.equal(source.destroyed, true);
    assert.equal(sink.destroyed, true);
    console.log('failure: accepted=2, final=false, streams destroyed');
  } else {
    await pipeline(source, sink);
    assert.equal(accepted, 4);
    assert.equal(finalCalled, true);
    console.log('success: accepted=4, final=true');
  }
}

await runExport(0);
await runExport(3);
```

本地实测输出如下。

```text
success: accepted=4, final=true
failure: accepted=2, final=false, streams destroyed
```

## 2. 把写入完成和发布分成两步

写文件时让 pipeline 输出到同目录临时路径，成功后再重命名，失败时删除临时文件。直接流式返回 HTTP 也能传播失败，但已发送的响应头无法改写为 500，客户端应处理下载中断。需要让用户只看到完整导出时，先生成产物，再提供下载地址。

highWaterMark 是触发背压的阈值。一个大块仍可能超过阈值，变换流两端也各有缓冲。要给内存估算上限，还要检查块大小、压缩器缓冲和并发导出数。若 Transform 内部先把全量数据收集到数组，外部 pipeline 无法为这部分提供背压。

## 小结

等待 pipeline 完成后才记录导出成功。查失败时同时看最后成功写入的块、目标端错误，以及临时文件的清理结果。

## 数据来源与检索时间

检索时间 2026-11-13。

| 来源 | URL | 对应内容 |
|---|---|---|
| Node.js Stream | https://nodejs.org/api/stream.html | pipeline 错误传播、Writable 和背压 |

## 核验说明

- Node.js v24.19.0 验证成功与注入失败。写端为内存模拟，磁盘替换、网络传输和内存峰值属于部署场景。
