如何在异步Node.js脚本结束时统计处理的JS文件总数?
解决异步递归脚本结束后统计文件数量的问题
问题描述
我写了一个Node.js脚本,用来递归异步修改目录里的JS文件,包含search(dirname)查找JS文件和modify(filename)修改文件的函数。代码如下:
let totalFilesSearched = 0; const search = (dir) => { fs.readdir(dir, (err, list) => { if (err) return; list.forEach((filename) => { const filepath = path.join(dir, filename); if (filename.endsWith('.js')) { modify(filepath); } else if (fs.lstatSync(filepath).isDirectory()) { search(filepath); } }) }); } const modify = (filename) => { fs.readFile(filename, 'utf8', (err, data) => { if (err) console.log(err); // ... my modification code ... totalFilesSearched++; }); } search(args[0]) console.log(`Total files searched: ${totalFilesSearched}`);
现在的问题是,因为所有操作都是异步的,脚本会直接输出Total files searched: 0,我想等所有搜索和修改操作完成后再打印这个统计值,但search()和modify()都是异步的,不知道怎么处理。
解决方案
核心问题是异步操作的完成状态追踪,我们可以通过Promise封装异步函数,结合async/await或者用回调计数器的方式来实现等待所有操作完成。下面给你两种可行的实现方式:
方式一:Promise+async/await(推荐,代码更清晰易维护)
Node.js从v10开始提供了fs.promises模块,直接支持Promise形式的文件操作,我们可以用它来封装search和modify,再通过async/await等待所有异步操作结束。
修改后的代码:
const fs = require('fs').promises; const path = require('path'); const args = process.argv.slice(2); let totalFilesSearched = 0; // 封装为异步函数,返回Promise const search = async (dir) => { try { const fileList = await fs.readdir(dir); // 收集当前目录下所有异步操作的Promise const taskPromises = fileList.map(async (filename) => { const fullPath = path.join(dir, filename); const stats = await fs.lstat(fullPath); // 异步获取文件状态,避免阻塞 if (filename.endsWith('.js')) { await modify(fullPath); } else if (stats.isDirectory()) { await search(fullPath); // 递归搜索子目录 } }); // 等待当前目录下所有任务完成 await Promise.all(taskPromises); } catch (err) { console.error('目录搜索出错:', err); } } const modify = async (filename) => { try { const fileContent = await fs.readFile(filename, 'utf8'); // ... 你的文件修改逻辑 ... totalFilesSearched++; } catch (err) { console.error('文件修改出错:', err); } } // 主入口函数,等待所有操作完成后打印统计值 const runScript = async () => { await search(args[0]); console.log(`Total files searched: ${totalFilesSearched}`); } runScript();
方式二:回调计数器(适合习惯回调风格的场景)
我们可以通过一个计数器追踪当前未完成的异步操作数量,当计数器归0时,就说明所有操作都执行完毕,此时再打印统计值。
修改后的代码:
const fs = require('fs'); const path = require('path'); const args = process.argv.slice(2); let totalFilesSearched = 0; let pendingTasks = 0; // 追踪未完成的异步任务数 const search = (dir, onComplete) => { pendingTasks++; fs.readdir(dir, (err, list) => { if (err) { console.error('目录搜索出错:', err); pendingTasks--; checkIfAllDone(onComplete); return; } list.forEach((filename) => { const fullPath = path.join(dir, filename); // 异步获取文件状态,避免同步阻塞 fs.lstat(fullPath, (err, stats) => { if (err) { console.error('获取文件状态出错:', err); pendingTasks--; checkIfAllDone(onComplete); return; } if (filename.endsWith('.js')) { modify(fullPath, () => { pendingTasks--; checkIfAllDone(onComplete); }); } else if (stats.isDirectory()) { search(fullPath, () => { pendingTasks--; checkIfAllDone(onComplete); }); } else { pendingTasks--; checkIfAllDone(onComplete); } }); }); // readdir操作完成,计数器减1 pendingTasks--; checkIfAllDone(onComplete); }); } const modify = (filename, onComplete) => { pendingTasks++; fs.readFile(filename, 'utf8', (err, data) => { if (err) console.error('文件修改出错:', err); // ... 你的文件修改逻辑 ... totalFilesSearched++; pendingTasks--; onComplete(); }); } // 检查所有任务是否完成 const checkIfAllDone = (callback) => { if (pendingTasks === 0) { callback(); } } // 启动脚本,等待完成后打印统计值 search(args[0], () => { console.log(`Total files searched: ${totalFilesSearched}`); });
关键说明
- 原代码的问题根源:
fs.readdir和fs.readFile都是异步非阻塞操作,脚本执行到console.log时,所有异步任务还没开始执行,所以totalFilesSearched还是初始值0。 - 推荐用Promise+async/await的写法:这是现代Node.js的标准异步处理方式,代码结构更清晰,也更容易排查问题。
- 替换同步方法:把原来的
fs.lstatSync改成异步的fs.lstat(或fs.promises.lstat),避免同步操作阻塞事件循环,尤其是处理大目录时,同步方法会导致脚本卡顿。
内容的提问来源于stack exchange,提问作者MarksCode
相关产品推荐
相关产品推荐

