Node.js 中 stream.pipeline 的错误处理与资源清理实践
在 Node.js 里用 `stream.pipeline` 串联多个流时,很多人只关注数据能不能正常流过去,等到线上出现文件描述符泄漏或者进程卡住才发现错误处理没写对。典型场景是文件上传后转码再写入磁盘,或者从数据库导出 CSV 再压缩响应给客户端。中途任何一环报错,如果没正确销毁上下游流,残留的句柄会越积越多,最终触发 EMFILE 或内存上涨。`pipeline` 相比 `pipe` 的核心优势就是它会自动销毁所有流并在回调里抛出第一个错误,但前提是你得用对回调形式,并且理解 `destroy` 的传播行为。下面从实际代码出发,梳理几个容易踩坑的细节:回调里重复抛错、自定义 Transform 忘记实现 `_destroy`、以及 HTTP 响应流提前关闭时的清理顺序。 先看一个常见但不够严谨的写法。下面这段代码把读取、gzip 压缩、写入文件串起来,错误只靠 `pipeline` 的回调处理: ```js const fs = require('fs'); const zlib = require('zlib'); const { pipeline } = require('stream'); function compressFile(src, dest, cb) { const source = fs.createReadStream(src); const gzip = zlib.createGzip(); const target = fs.createWriteStream(dest); pipeline(source, gzip, target, (err) => { if (err) { console.error('compress failed:', err); return cb(err); } cb(null); }); } ``` 这段代码本身能工作,`pipeline` 会在出错时销毁三个流。问题出在调用方如果忽略 `cb` 里的错误,比如直接 `compressFile(a, b, () => {})`,那错误就被吞了。更隐蔽的是,当 `target` 写入失败(磁盘满、权限问题),`pipeline` 会销毁 `source` 和 `gzip`,但 `fs.createWriteStream` 内部打开的文件描述符是否一定关闭,取决于流的 `_destroy` 实现。Node 核心流的实现是可靠的,可一旦你在中间插入自定义 Transform,就得自己保证清理逻辑。 自定义 Transform 最常见的疏漏是没有处理 `_destroy`。假设我们写一个限速转换流,内部持有一个定时器: ```js const { Transform } = require('stream'); class RateLimit extends Transform { constructor(bytesPerSec) { super(); this.bytesPerSec = bytesPerSec; this.timer = null; this.queue = []; } _transform(chunk, enc, cb) { this.queue.push(chunk); if (!this.timer) this._schedule(); cb(); } _schedule() { this.timer = setTimeout(() => { const chunk = this.queue.shift(); if (chunk) this.push(chunk); if (this.queue.length) { this._schedule(); } else { clearTimeout(this.timer); this.timer = null; } }, 1000 / this.bytesPerSec); } _destroy(err, cb) { if (this.timer) { clearTimeout(this.timer); this.timer = null; } this.queue = []; cb(err); } } ``` 如果省掉 `_destroy`,上游报错触发 `pipeline` 销毁这个 Transform 时,定时器还在跑,`this.push` 可能往已销毁的流里写数据,Node 会抛 `ERR_STREAM_PUSH_AFTER_EOF` 或者干脆静默泄漏一个定时器让进程不退出。补上 `_destroy` 并调用 `cb(err)` 之后,错误才能沿着管道继续向上传递。这里有个细节:`_destroy` 的 `cb` 必须调用,而且传入的 `err` 会作为 `pipeline` 回调拿到的错误;如果你在 `_destroy` 里又抛同步异常,错误会变成未捕获异常,所以清理代码要包在 try/catch 里。 HTTP 场景下还有一个顺序问题。把文件流通过 `pipeline` 写到 `res` 时,客户端提前断开连接会让 `res` 触发 `close`,此时 `pipeline` 会销毁源文件流。下面这种写法在断开瞬间容易触发 `ERR_STREAM_PREMATURE_CLOSE`: ```js app.get('/download', (req, res) => { const file = fs.createReadStream('/data/report.zip'); pipeline(file, res, (err) => { if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') { console.error(err); if (!res.headersSent) res.status(500).end(); } }); }); ``` 客户端主动断开的 `ERR_STREAM_PREMATURE_CLOSE` 属于预期行为,不该当成服务端错误打日志。但更关键的是别在回调里再往 `res` 写数据——流已经销毁,`res.end()` 会抛 `ERR_STREAM_DESTROYED`。判断 `res.headersSent` 再决定是否响应,能避免二次异常。如果还需要在断开时做额外清理,比如删除临时文件,应该监听 `res` 的 `close` 事件而不是依赖 `pipeline` 回调里的错误码,因为错误码只能说明“提前关闭”,不区分是客户端断开还是上游读失败。 `pipeline` 的 Promise 版本在 `stream/promises` 里,用 `await` 时错误处理更直观,但要注意它同样不会自动捕获下游监听器的异常。比如下面这段: ```js const { pipeline } = require('stream/promises'); async function uploadToS3(readable, s3Stream) { await pipeline(readable, s3Stream); } ``` 如果 `s3Stream` 是第三方库返回的 Writable,它内部可能在 `_write` 里触发异步错误但不走回调,`pipeline` 无法感知。这种只能靠库自己的 `error` 事件,或者在包装层加一个 `PassThrough` 手动转发错误。另一个实际经验是:`pipeline` 只负责流之间的错误传播,不负责业务层面的重试。文件转码失败想重试,得在调用 `pipeline` 的外层用循环或递归,并且每次重建所有流实例——被销毁的流不能复用。 调试流问题时,`pipeline` 回调里的 `err` 有时候只带一个笼统的 `ERR_STREAM_PREMATURE_CLOSE`,看不出是哪一环先挂的。可以在每个关键节点插入一个轻量的 `Transform` 打点,记录 `_destroy` 被调用的顺序: ```js function tracer(name) { return new Transform({ transform(chunk, enc, cb) { cb(null, chunk); }, destroy(err, cb) { console.log(`${name} destroyed`, err && err.message); cb(err); } }); } await pipeline( fs.createReadStream(src), tracer('source'), zlib.createGzip(), tracer('gzip'), fs.createWriteStream(dest), tracer('target') ); ``` `pipeline` 从出错点开始向上游和下游同时销毁,销毁顺序并不保证与管道顺序一致,所以日志里看到 `target` 先于 `gzip` 被销毁是正常的。理解这一点,排查“为什么某个流的 close 事件没触发”时就不会误判。 推荐查看天气:http://www.shuyiwang.cn/zi/3192.html 推荐查看天气:http://www.shuyiwang.cn/zi/3193.html 推荐查看天气:http://www.shuyiwang.cn/zi/3194.html 推荐查看天气:http://www.shuyiwang.cn/zi/3195.html 推荐查看天气:http://www.shuyiwang.cn/zi/3196.html 推荐查看天气:http://www.shuyiwang.cn/zi/3197.html 推荐查看天气:http://www.shuyiwang.cn/zi/3198.html 推荐查看天气:http://www.shuyiwang.cn/zi/3199.html 推荐查看天气:http://www.shuyiwang.cn/zi/3200.html 推荐查看天气:http://www.shuyiwang.cn/zi/3201.html 推荐查看天气:http://www.shuyiwang.cn/zi/3202.html 推荐查看天气:http://www.shuyiwang.cn/zi/3203.html 推荐查看天气:http://www.shuyiwang.cn/zi/3204.html 推荐查看天气:http://www.shuyiwang.cn/zi/3205.html 推荐查看天气:http://www.shuyiwang.cn/zi/3206.html 推荐查看天气:http://www.shuyiwang.cn/zi/3207.html 推荐查看天气:http://www.shuyiwang.cn/zi/3208.html 推荐查看天气:http://www.shuyiwang.cn/zi/3209.html 推荐查看天气:http://www.shuyiwang.cn/zi/3210.html 推荐查看天气:http://www.shuyiwang.cn/zi/3211.html 推荐查看天气:http://www.shuyiwang.cn/zi/3212.html 推荐查看天气:http://www.shuyiwang.cn/zi/3213.html 推荐查看天气:http://www.shuyiwang.cn/zi/3214.html 推荐查看天气:http://www.shuyiwang.cn/zi/3215.html 推荐查看天气:http://www.shuyiwang.cn/zi/3216.html 推荐查看天气:http://www.shuyiwang.cn/zi/3217.html 推荐查看天气:http://www.shuyiwang.cn/zi/3218.html 推荐查看天气:http://www.shuyiwang.cn/zi/3219.html 推荐查看天气:http://www.shuyiwang.cn/zi/3220.html 推荐查看天气:http://www.shuyiwang.cn/zi/3221.html 推荐查看天气:http://www.shuyiwang.cn/zi/3222.html 推荐查看天气:http://www.shuyiwang.cn/zi/3223.html 推荐查看天气:http://www.shuyiwang.cn/zi/3224.html 推荐查看天气:http://www.shuyiwang.cn/zi/3225.html 推荐查看天气:http://www.shuyiwang.cn/zi/3226.html 推荐查看天气:http://www.shuyiwang.cn/zi/3227.html 推荐查看天气:http://www.shuyiwang.cn/zi/3228.html 推荐查看天气:http://www.shuyiwang.cn/zi/3229.html 推荐查看天气:http://www.shuyiwang.cn/zi/3230.html 推荐查看天气:http://www.shuyiwang.cn/zi/3231.html 推荐查看天气:http://www.shuyiwang.cn/zi/3232.html 推荐查看天气:http://www.shuyiwang.cn/zi/3233.html 推荐查看天气:http://www.shuyiwang.cn/zi/3234.html 推荐查看天气:http://www.shuyiwang.cn/zi/3235.html 推荐查看天气:http://www.shuyiwang.cn/zi/3236.html 推荐查看天气:http://www.shuyiwang.cn/zi/3237.html 推荐查看天气:http://www.shuyiwang.cn/zi/3238.html 推荐查看天气:http://www.shuyiwang.cn/zi/3239.html 推荐查看天气:http://www.shuyiwang.cn/zi/3240.html 推荐查看天气:http://www.shuyiwang.cn/zi/3241.html 推荐查看天气:http://www.shuyiwang.cn/zi/3242.html 推荐查看天气:http://www.shuyiwang.cn/zi/3243.html 推荐查看天气:http://www.shuyiwang.cn/zi/3244.html 推荐查看天气:http://www.shuyiwang.cn/zi/3245.html 推荐查看天气:http://www.shuyiwang.cn/zi/3246.html 推荐查看天气:http://www.shuyiwang.cn/zi/3247.html 推荐查看天气:http://www.shuyiwang.cn/zi/3248.html 推荐查看天气:http://www.shuyiwang.cn/zi/3249.html 推荐查看天气:http://www.shuyiwang.cn/zi/3250.html 推荐查看天气:http://www.shuyiwang.cn/zi/3251.html 推荐查看天气:http://www.shuyiwang.cn/zi/3252.html 推荐查看天气:http://www.shuyiwang.cn/zi/3253.html 推荐查看天气:http://www.shuyiwang.cn/zi/3254.html 推荐查看天气:http://www.shuyiwang.cn/zi/3255.html 推荐查看天气:http://www.shuyiwang.cn/zi/3256.html 推荐查看天气:http://www.shuyiwang.cn/zi/3257.html 推荐查看天气:http://www.shuyiwang.cn/zi/3258.html 推荐查看天气:http://www.shuyiwang.cn/zi/3259.html 推荐查看天气:http://www.shuyiwang.cn/zi/3260.html 推荐查看天气:http://www.shuyiwang.cn/zi/3261.html 推荐查看天气:http://www.shuyiwang.cn/zi/3262.html 推荐查看天气:http://www.shuyiwang.cn/zi/3263.html 推荐查看天气:http://www.shuyiwang.cn/zi/3264.html 推荐查看天气:http://www.shuyiwang.cn/zi/3265.html 推荐查看天气:http://www.shuyiwang.cn/zi/3266.html 推荐查看天气:http://www.shuyiwang.cn/zi/3267.html 推荐查看天气:http://www.shuyiwang.cn/zi/3268.html 推荐查看天气:http://www.shuyiwang.cn/zi/3269.html 推荐查看天气:http://www.shuyiwang.cn/zi/3270.html 推荐查看天气:http://www.shuyiwang.cn/zi/3271.html 推荐查看天气:http://www.shuyiwang.cn/zi/3272.html 推荐查看天气:http://www.shuyiwang.cn/zi/3273.html 推荐查看天气:http://www.shuyiwang.cn/zi/3274.html 推荐查看天气:http://www.shuyiwang.cn/zi/3275.html 推荐查看天气:http://www.shuyiwang.cn/zi/3276.html 推荐查看天气:http://www.shuyiwang.cn/zi/3277.html 推荐查看天气:http://www.shuyiwang.cn/zi/3278.html 推荐查看天气:http://www.shuyiwang.cn/zi/3279.html 推荐查看天气:http://www.shuyiwang.cn/zi/3280.html 推荐查看天气:http://www.shuyiwang.cn/zi/3281.html 推荐查看天气:http://www.shuyiwang.cn/zi/3282.html 推荐查看天气:http://www.shuyiwang.cn/zi/3283.html 推荐查看天气:http://www.shuyiwang.cn/zi/3284.html 推荐查看天气:http://www.shuyiwang.cn/zi/3285.html 推荐查看天气:http://www.shuyiwang.cn/zi/3286.html 推荐查看天气:http://www.shuyiwang.cn/zi/3287.html 推荐查看天气:http://www.shuyiwang.cn/zi/3288.html 推荐查看天气:http://www.shuyiwang.cn/zi/3289.html 推荐查看天气:http://www.shuyiwang.cn/zi/3290.html 推荐查看天气:http://www.shuyiwang.cn/zi/3291.html 推荐查看天气:http://www.shuyiwang.cn/zi/3292.html 推荐查看天气:http://www.shuyiwang.cn/zi/3293.html 推荐查看天气:http://www.shuyiwang.cn/zi/3294.html 推荐查看天气:http://www.shuyiwang.cn/zi/3295.html 推荐查看天气:http://www.shuyiwang.cn/zi/3296.html 推荐查看天气:http://www.shuyiwang.cn/zi/3297.html 推荐查看天气:http://www.shuyiwang.cn/zi/3298.html 推荐查看天气:http://www.shuyiwang.cn/zi/3299.html 推荐查看天气:http://www.shuyiwang.cn/zi/3300.html 推荐查看天气:http://www.shuyiwang.cn/zi/3301.html 推荐查看天气:http://www.shuyiwang.cn/zi/3302.html 推荐查看天气:http://www.shuyiwang.cn/zi/3303.html 推荐查看天气:http://www.shuyiwang.cn/zi/3304.html 推荐查看天气:http://www.shuyiwang.cn/zi/3305.html 推荐查看天气:http://www.shuyiwang.cn/zi/3306.html 推荐查看天气:http://www.shuyiwang.cn/zi/3307.html 推荐查看天气:http://www.shuyiwang.cn/zi/3308.html 推荐查看天气:http://www.shuyiwang.cn/zi/3309.html 推荐查看天气:http://www.shuyiwang.cn/zi/3310.html 推荐查看天气:http://www.shuyiwang.cn/zi/3311.html 推荐查看天气:http://www.shuyiwang.cn/zi/3312.html 推荐查看天气:http://www.shuyiwang.cn/zi/3313.html 推荐查看天气:http://www.shuyiwang.cn/zi/3314.html 推荐查看天气:http://www.shuyiwang.cn/zi/3315.html 推荐查看天气:http://www.shuyiwang.cn/zi/3316.html 推荐查看天气:http://www.shuyiwang.cn/zi/3317.html 推荐查看天气:http://www.shuyiwang.cn/zi/3318.html 推荐查看天气:http://www.shuyiwang.cn/zi/3319.html 推荐查看天气:http://www.shuyiwang.cn/zi/3320.html 推荐查看天气:http://www.shuyiwang.cn/zi/3321.html 推荐查看天气:http://www.shuyiwang.cn/zi/3322.html 推荐查看天气:http://www.shuyiwang.cn/zi/3323.html 推荐查看天气:http://www.shuyiwang.cn/zi/3324.html 推荐查看天气:http://www.shuyiwang.cn/zi/3325.html 推荐查看天气:http://www.shuyiwang.cn/zi/3326.html 推荐查看天气:http://www.shuyiwang.cn/zi/3327.html 推荐查看天气:http://www.shuyiwang.cn/zi/3328.html 推荐查看天气:http://www.shuyiwang.cn/zi/3329.html 推荐查看天气:http://www.shuyiwang.cn/zi/3330.html 推荐查看天气:http://www.shuyiwang.cn/zi/3331.html 推荐查看天气:http://www.shuyiwang.cn/zi/3332.html 推荐查看天气:http://www.shuyiwang.cn/zi/3333.html 推荐查看天气:http://www.shuyiwang.cn/zi/3334.html 推荐查看天气:http://www.shuyiwang.cn/zi/3335.html 推荐查看天气:http://www.shuyiwang.cn/zi/3336.html 推荐查看天气:http://www.shuyiwang.cn/zi/3337.html 推荐查看天气:http://www.shuyiwang.cn/zi/3338.html 推荐查看天气:http://www.shuyiwang.cn/zi/3339.html 推荐查看天气:http://www.shuyiwang.cn/zi/3340.html 推荐查看天气:http://www.shuyiwang.cn/zi/3341.html 推荐查看天气:http://www.shuyiwang.cn/zi/3342.html 推荐查看天气:http://www.shuyiwang.cn/zi/3343.html 推荐查看天气:http://www.shuyiwang.cn/zi/3344.html 推荐查看天气:http://www.shuyiwang.cn/zi/3345.html 推荐查看天气:http://www.shuyiwang.cn/zi/3346.html 推荐查看天气:http://www.shuyiwang.cn/zi/3347.html 推荐查看天气:http://www.shuyiwang.cn/zi/3348.html 推荐查看天气:http://www.shuyiwang.cn/zi/3349.html 推荐查看天气:http://www.shuyiwang.cn/zi/3350.html 推荐查看天气:http://www.shuyiwang.cn/zi/3351.html 推荐查看天气:http://www.shuyiwang.cn/zi/3352.html 推荐查看天气:http://www.shuyiwang.cn/zi/3353.html 推荐查看天气:http://www.shuyiwang.cn/zi/3354.html 推荐查看天气:http://www.shuyiwang.cn/zi/3355.html 推荐查看天气:http://www.shuyiwang.cn/zi/3356.html 推荐查看天气:http://www.shuyiwang.cn/zi/3357.html 推荐查看天气:http://www.shuyiwang.cn/zi/3358.html 推荐查看天气:http://www.shuyiwang.cn/zi/3359.html 推荐查看天气:http://www.shuyiwang.cn/zi/3360.html 推荐查看天气:http://www.shuyiwang.cn/zi/3361.html 推荐查看天气:http://www.shuyiwang.cn/zi/3362.html 推荐查看天气:http://www.shuyiwang.cn/zi/3363.html 推荐查看天气:http://www.shuyiwang.cn/zi/3364.html 推荐查看天气:http://www.shuyiwang.cn/zi/3365.html 推荐查看天气:http://www.shuyiwang.cn/zi/3366.html 推荐查看天气:http://www.shuyiwang.cn/zi/3367.html 推荐查看天气:http://www.shuyiwang.cn/zi/3368.html 推荐查看天气:http://www.shuyiwang.cn/zi/3369.html 推荐查看天气:http://www.shuyiwang.cn/zi/3370.html 推荐查看天气:http://www.shuyiwang.cn/zi/3371.html 推荐查看天气:http://www.shuyiwang.cn/zi/3372.html 推荐查看天气:http://www.shuyiwang.cn/zi/3373.html 推荐查看天气:http://www.shuyiwang.cn/zi/3374.html 推荐查看天气:http://www.shuyiwang.cn/zi/3375.html 推荐查看天气:http://www.shuyiwang.cn/zi/3376.html 推荐查看天气:http://www.shuyiwang.cn/zi/3377.html 推荐查看天气:http://www.shuyiwang.cn/zi/3378.html 推荐查看天气:http://www.shuyiwang.cn/zi/3379.html 推荐查看天气:http://www.shuyiwang.cn/zi/3380.html 推荐查看天气:http://www.shuyiwang.cn/zi/3381.html 推荐查看天气:http://www.shuyiwang.cn/zi/3382.html 推荐查看天气:http://www.shuyiwang.cn/zi/3383.html 推荐查看天气:http://www.shuyiwang.cn/zi/3384.html 推荐查看天气:http://www.shuyiwang.cn/zi/3385.html 推荐查看天气:http://www.shuyiwang.cn/zi/3386.html 推荐查看天气:http://www.shuyiwang.cn/zi/3387.html 推荐查看天气:http://www.shuyiwang.cn/zi/3388.html 推荐查看天气:http://www.shuyiwang.cn/zi/3389.html 推荐查看天气:http://www.shuyiwang.cn/zi/3390.html 推荐查看天气:http://www.shuyiwang.cn/zi/3391.html