NodeJS:管道至WriteStream并等待finish事件后文件不存在问题排查
问题分析与解决方案
首先,我来帮你拆解代码里导致文件未创建却触发finish事件的几个核心问题:
关键错误点
1. 多余的writable.end()调用
你在finish事件处理函数里调用了writable.end(),但finish事件本身就是可写流完成所有写入并关闭后才会触发的。手动调用这个方法不仅完全没必要,还可能干扰流的正常生命周期,甚至导致文件系统操作异常。
2. 未监听响应流(res)的错误
你只监听了管道和可写流的错误,但忽略了响应流本身的错误。如果下载过程中出现网络中断、服务器断开连接等问题,res会触发error事件,但这个错误不会被你的代码捕获,最终导致可写流没有接收到任何数据就触发finish,自然不会创建文件。
3. 空响应体的情况未处理
当服务器返回200状态码但没有响应体时,res.pipe(writable)不会向可写流写入任何数据。在部分Node.js版本或文件系统环境下,这种空写入操作可能不会生成目标文件,而你的代码没有在finish事件中提前检查文件是否存在。
4. 校验流程的错误处理不完整
当读取文件进行校验时,如果文件不存在,你只打印了日志却没有把错误传递给reject,导致后续流程无法正确处理这个异常。
修正后的代码
const fs = require('fs'); const crypto = require('crypto'); exports.downloadHttpFile = function(url, path, mimeType, checksum) { return exports.get(url, mimeType, false).then(function(res) { if (res.statusCode !== 200) { return Promise.reject(new Error('Invalid response from server when trying to download file')); } return new Promise(function(resolve, reject) { console.log('creating file at ' + path); const writable = fs.createWriteStream(path); // 监听响应流的网络错误,避免遗漏 res.once('error', reject); // 监听可写流的文件系统错误 writable.once('error', reject); writable.once('finish', function() { // 先检查文件是否存在,避免后续读取不存在的文件 fs.access(path, fs.constants.F_OK, function(accessErr) { if (accessErr) { console.log('File was not created after stream finish'); return reject(new Error('Downloaded file does not exist')); } // 没有校验和直接返回路径 if (!checksum) { return resolve(path); } // 校验校验和对象的完整性 if (!checksum.hashType || !checksum.hashEncoding || !checksum.hashValue) { return reject(new Error('Invalid checksum object')); } // 计算文件哈希并校验 const hash = crypto.createHash(checksum.hashType).setEncoding(checksum.hashEncoding); fs.createReadStream(path) .once('error', reject) .pipe(hash) .once('finish', function() { const fileHash = this.read(); if (fileHash !== checksum.hashValue) { // 删除不匹配的文件,异步处理避免阻塞 fs.unlink(path, (unlinkErr) => { if (unlinkErr) console.warn('Failed to delete mismatched file:', unlinkErr); reject(new Error('Downloaded file checksum did not match given checksum')); }); } else { resolve(path); } }) .once('error', reject); }); }); // 启动流管道传输 res.pipe(writable); }); }); };
修正说明
- 移除多余的
writable.end():让流的生命周期由pipe自动管理,避免手动干预导致的异常。 - 添加响应流错误监听:确保网络层面的错误能被捕获并传递给Promise的
reject。 - 增加文件存在性检查:在
finish事件触发后,先用fs.access异步检查文件是否存在,提前拦截文件未创建的情况。 - 优化校验和不匹配的文件删除逻辑:使用异步的
fs.unlink处理文件删除,并捕获删除时的错误,避免影响主流程。 - 简化事件绑定逻辑:让代码结构更清晰,错误处理路径更完整。
内容的提问来源于stack exchange,提问作者Eladian
相关产品推荐
相关产品推荐

