Node.js未调用fileHandle.open()仍提示需close()的原因
触发警告的根本原因
你看到的DEP0137 DeprecationWarning,核心触发逻辑是Node.js检测到存在未被显式调用close()释放的FileHandle对象被垃圾回收机制回收。
你之前了解到的「仅通过fileHandle.open()方式打开文件时才需要显式关闭句柄」结论本身没有错误,当前代码里的未释放句柄不是你业务代码显式创建的,而是来自以下几个场景:
- 如果你使用的是14.x~16.x早期版本的Node.js,
fs/promises模块下的readFile、writeFile、mkdir等API存在已知的内部句柄泄漏bug:在高频IO场景下,API内部临时打开的文件描述符不会在操作完成后立刻释放,会等到垃圾回收触发时才被关闭,直接触发该警告。 - 你的代码存在不必要的重复IO调用:固定路径
./FolderName的创建逻辑放在了千次级别的for循环内部,虽然配置了recursive: true不会重复报错,但每次调用都会触发系统级的文件系统检查,短时间内大量IO请求堆积会导致部分临时文件句柄排队超时、失去引用,最终被GC回收时触发警告。 - 批量写文件的逻辑没有并发控制:你用
Promise.all一次性发起所有png文件的写入请求,千次循环叠加批量写入的并发量,会瞬间打满Node.js进程的文件描述符配额,部分写入请求的内部句柄在排队过程中失去上下文引用,被GC回收时就会抛出警告。
可直接落地的修复方案
- 优先将Node.js升级到18.x及以上的LTS稳定版本,上述fs promise API的内部句柄泄漏bug在这些版本中已经全部修复,绝大多数场景下升级后警告会直接消失。
- 将固定目录的创建逻辑移到for循环外部,仅在程序启动阶段执行一次即可,减少无意义的IO系统调用:
const dir = './FolderName'; await fs.mkdir(dir, { recursive: true }); // 移到循环外 for(let i = 0; i < n; i++){ // 原有循环逻辑,去掉内部的mkdir调用 }
- 给批量文件写入加并发控制,不要用
Promise.all一次性发起所有IO请求,建议将并发数控制在10~20区间,避免瞬间占满文件描述符池。可以用简单的并发池实现替换原有逻辑:
// 通用并发控制函数 async function asyncPool(concurrency, taskFactories) { const results = []; const executing = new Set(); for (const factory of taskFactories) { const task = Promise.resolve().then(factory); results.push(task); executing.add(task); const cleanup = () => executing.delete(task); task.then(cleanup).catch(cleanup); if (executing.size >= concurrency) { await Promise.race(executing); } } return Promise.all(results); } // 替换原有Promise.all的写入逻辑 await asyncPool(10, arr.map((arrItem, index) => () => fs.writeFile(`${dir}/${index + 1}.png`, arrItem) ));
- 如果完成以上操作后警告仍然存在,可以在启动程序时加上
--trace-warnings参数运行,控制台会直接打印出警告对应的具体调用栈,精确定位泄漏的句柄来自哪一行代码:node --trace-warnings your-entry-file.js
内容的提问来源于stack exchange,提问作者sayandcode
相关产品推荐
相关产品推荐

