Node.js中fs.appendFile引发EMFILE错误:为何未正确关闭文件?
fs.appendFile批量调用会触发EMFILE错误? 你完全猜对了问题的核心!这个EMFILE: too many open files错误,就是因为你短时间内发起了大量fs.appendFile调用,导致系统同时打开的文件描述符数量超过了上限。
背后的原因
fs.appendFile是异步非阻塞的API,每次调用都会独立执行「打开文件 → 写入内容 → 关闭文件」的流程。当你用forEach一口气发起10000次调用时,这些异步操作会几乎同时启动——也就是说,在前面的调用还没来得及关闭文件的时候,后面的调用已经又打开了同一个文件。而操作系统对单个进程能同时持有的文件描述符数量有默认限制(比如多数系统默认是1024),当同时打开的文件数突破这个阈值,就会抛出这个错误。
为什么写入流不会有这个问题?
fs.createWriteStream的工作方式完全不同:它会一次性打开目标文件并保持文件描述符的持有状态,所有后续的write操作都会复用这个已打开的文件描述符,直到你主动调用stream.end()或者流完成所有写入后自动关闭。这种复用机制既减少了频繁打开/关闭文件的开销,也从根本上避免了文件描述符耗尽的问题,非常适合大批量的连续写入场景。
解决方案
1. 使用写入流(最推荐)
这是性能最优、代码也简洁的方案:
const fs = require('fs'); // 用'a'标记指定追加模式,和appendFile行为一致 const writeStream = fs.createWriteStream('append.txt', { flags: 'a' }); [...Array(10000)].forEach((_, index) => { writeStream.write(`${index}\n`, (err) => { if (err) console.error('写入失败:', err); }); }); // 所有写入任务发起后,关闭流 writeStream.end(() => { console.log('所有内容已成功追加到文件'); });
2. 限制并发数(如果坚持用appendFile)
通过控制同时执行的fs.appendFile数量,避免同时打开过多文件。可以借助async库的eachLimit来实现:
const fs = require('fs'); const async = require('async'); // 先执行 npm install async 安装 // 限制同时最多100个异步操作在执行 const MAX_CONCURRENT = 100; async.eachLimit([...Array(10000)], MAX_CONCURRENT, (_, callback) => { const index = _; fs.appendFile('append.txt', `${index}\n`, callback); }, (err) => { if (err) console.error('批量追加失败:', err); else console.log('所有内容追加完成'); });
3. 改用同步版本(不推荐)
fs.appendFileSync是同步阻塞的API,每次调用都会完成完整的打开-写入-关闭流程后才会执行下一次,不会同时打开多个文件。但缺点是会阻塞Node.js的事件循环,大批量操作时会严重影响性能:
const fs = require('fs'); [...Array(10000)].forEach((_, index) => { try { fs.appendFileSync('append.txt', `${index}\n`); } catch (err) { console.error('写入失败:', err); } });
总结
简单来说,fs.appendFile适合零散、低频率的追加操作;如果是大批量连续写入,写入流是绝对的最优选择——既高效又能避免资源耗尽的问题。
内容的提问来源于stack exchange,提问作者jack_snipe

