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

如何在异步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}`);
});

关键说明

  1. 原代码的问题根源:fs.readdir和fs.readFile都是异步非阻塞操作,脚本执行到console.log时,所有异步任务还没开始执行,所以totalFilesSearched还是初始值0。
  2. 推荐用Promise+async/await的写法:这是现代Node.js的标准异步处理方式,代码结构更清晰,也更容易排查问题。
  3. 替换同步方法:把原来的fs.lstatSync改成异步的fs.lstat(或fs.promises.lstat),避免同步操作阻塞事件循环,尤其是处理大目录时,同步方法会导致脚本卡顿。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:07:40