You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js中fs.createReadStream的ENOENT错误无法捕获如何解决

问题背景

我在使用Node.js流管道开展文件处理相关开发时,计划测试各类错误场景,第一个测试用例为打开不存在的文件,但测试后发现无法捕获该场景下抛出的错误,fs模块并不会触发预期的error事件,该问题在fs.unlink接口上也同样存在。

复现代码

const fName = 'C:/temp/fff.txt';
const outStream = fs.createWriteStream(fName + '.enc');
console.log(`-- 1111 ${fName} `);
fs.createReadStream(fName)
.pipe(outStream)
.on('open', () => {
  console.log(`ON OPEN  ${fName}`);
})
.on('error', (err) => {
  console.log(`ON ERROR ${fName}; an error happened: ${err.message}`);
})
.on('close', () => {
  console.log(`ON CLOSE  ${fName}`);
})
.on('end', () => {
  console.log(`ON END  ${fName}`);
});
console.log(`-- 2222 ${fName} `);

运行现象

  • 目标文件存在时,运行输出:
-- 1111 C:/temp/fff.txt
-- 2222 C:/temp/fff.txt
...
ON OPEN  C:/temp/fff.txt
ON CLOSE  C:/temp/fff.txt
  • 目标文件不存在时,运行直接抛出未捕获异常:
-- 1111 C:/temp/fff.txt
-- 2222 C:/temp/fff.txt
...
ON OPEN  C:/temp/fff.txt
2022-06-13T19:28:48.379Z; error; uncaughtException: ENOENT: no such file or directory, open 'C:\temp\ffff.txt'
Error: ENOENT: no such file or directory, open 'C:\temp\ffff.txt';

C:\Users\p>

有开发者提出猜想:是否存在error事件在stream.on('error', ...)绑定逻辑生效前就已经被触发的可能性?

问题根因

这个猜想完全正确,问题核心出在两个点:

  1. 事件绑定时机错误:fs.createReadStream()执行后会立刻在同步阶段发起文件打开操作,代码中是在调用.pipe(outStream)之后才绑定error监听,此时读取流打开不存在文件触发的ENOENT错误已经发出,没有对应监听器的情况下会直接作为未捕获异常抛出。
  2. 原生pipe()方法不会自动转发上游读取流的错误到下游返回的流上,代码中绑定的error监听实际是加在写入流outStream上,根本接收不到读取流抛出的错误。
正确处理方案
  • 方案1:提前为所有流单独绑定错误监听,再执行pipe操作
    先分别创建读取流、写入流,给两个流都挂载好error事件监听,再调用pipe方法,出错时记得手动销毁未关闭的流避免资源泄漏:
    const fName = 'C:/temp/fff.txt';
    const readStream = fs.createReadStream(fName);
    const outStream = fs.createWriteStream(fName + '.enc');
    
    // 提前绑定错误监听
    readStream.on('error', (err) => {
      console.log(`读取文件失败: ${err.message}`);
      outStream.destroy();
    });
    outStream.on('error', (err) => {
      console.log(`写入文件失败: ${err.message}`);
      readStream.destroy();
    });
    
    // 绑定完监听再执行pipe
    readStream.pipe(outStream)
      .on('open', () => {
        console.log(`ON OPEN  ${fName}`);
      })
      .on('close', () => {
        console.log(`ON CLOSE  ${fName}`);
      })
      .on('end', () => {
        console.log(`ON END  ${fName}`);
      });
    
  • 方案2:使用官方推荐的pipeline方法替代原生pipe(Node.js 10+支持)
    stream.pipeline会自动转发上下游所有流的错误,在处理完成或出错时自动销毁所有关联流,从根源避免资源泄漏和错误捕获不到的问题,支持回调和Promise两种调用方式:
    const fs = require('fs');
    const { pipeline } = require('stream/promises');
    
    async function processFile() {
      const fName = 'C:/temp/fff.txt';
      try {
        await pipeline(
          fs.createReadStream(fName),
          fs.createWriteStream(fName + '.enc')
        );
        console.log('文件处理完成');
      } catch (err) {
        console.log(`文件处理失败: ${err.message}`);
      }
    }
    
    processFile();
    
  • 针对fs.unlink的同类问题:fs.unlink是标准的异步回调接口,错误会通过回调函数的第一个参数返回,使用时需要在回调中判断处理错误;如果是Promise版本的fs.unlink,直接通过try/catch捕获即可。

内容的提问来源于stack exchange,提问作者VladP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 17:21:09