为何fs.createWriteStream抛出的错误在Jest中无法被捕获?
为什么fs.createWriteStream的错误无法在Jest中被try/catch或Promise.catch捕获?
这个问题我之前也碰到过,核心原因是fs.createWriteStream是基于事件驱动的API,并非Promise友好的,你当前的错误没有被纳入Promise链的处理流程,所以try/catch、.then/.catch甚至Jest的rejects.toThrow都没法正确捕获它。
问题根源拆解
当你调用fun(t)时,它内部大概率使用了fs.createWriteStream来写入文件,但这个流的错误是通过error事件触发的,而不是通过Promise的reject状态抛出。也就是说:
- 你的
fun(t)返回的Promise并没有绑定流的error事件,所以即使流抛出错误,Promise也不会进入reject状态 - 这种情况下,错误会“逃逸”出Promise链,变成Jest能检测到的未捕获异常,这就是你看到直接输出ENOENT错误的原因
对比你之前的rawFileDownloaderFactory测试能正常工作,是因为它依赖的fetch是原生Promise API,或者内部已经把所有回调式的错误都转化成了Promise的reject,所以错误能被Jest的rejects.toThrow正确捕获。
解决方案
1. 先修复datafileWriterFactory的内部实现
你需要把fs.createWriteStream的操作Promise化,手动绑定流的error和finish事件来控制Promise的状态:
// 示例:在datafileWriterFactory内部封装stream操作 const fs = require('fs'); function writeToFile(path, content) { return new Promise((resolve, reject) => { const writeStream = fs.createWriteStream(path); writeStream.write(content); writeStream.end(); // 流写入完成时resolve Promise writeStream.on('finish', () => resolve()); // 关键:把流的error事件转化为Promise的reject writeStream.on('error', (err) => reject(err)); }); }
这样,当流触发error事件时,Promise会进入reject状态,fun(t)返回的Promise就会带上这个错误,后续的错误处理逻辑就能生效了。
2. 修正测试代码的写法
确保你用Jest的正确语法测试Promise的reject,不需要额外的try/catch或.then/.catch(除非你需要调试):
test('throws when it cannot write to the file', async () => { const fun = datafileWriterFactory(fetch)(selector, parser)(noop); // 直接测试Promise的reject,Jest会自动等待并验证 await expect(fun(t)).rejects.toThrow(); // 如果需要更精确的验证,可以指定错误类型或消息 // await expect(fun(t)).rejects.toThrow('ENOENT'); // await expect(fun(t)).rejects.toThrow('no such file or directory'); });
补充:为什么原有测试代码没捕获到错误?
你的原有测试代码中:
.then(() => console.log("WORKED"))不会触发,因为流没完成写入就报错了.catch((e) => console.error("FAILED", e))不会触发,因为Promise没有进入reject状态- 外层的try/catch也抓不到,因为
await的Promise没有抛出reject,流的错误是独立的未捕获事件
Jest会自动检测到这类未捕获的异常,所以直接输出了错误信息。
内容的提问来源于stack exchange,提问作者GTF
相关产品推荐
相关产品推荐

