Node.js 如何同步获取指定目录下所有文件?

在基于 Node.js 编写程序,时常会遇到类似这样的需求:“列出(遍历)目录下的所有文件,包括子目录“。如何实现这一点呢,很显然可以使用一些已写好的工具库,如 node-rdnode-walkglob 等;但她们或多或少,都有些弊端,不方便使用;那么手动写一个方法,来实现“同步获取指定目录下所有文件“呢?本篇文章,即同大家一起探讨下这个问题。

如果要获得指定目录下文件,您可以使用 fs.readdirfs.readdirSync 方法。fs 包含在 Node.js 核心中,因此无需安装任何软件。这非常简单,具体代码如下代码:

const fs = require('fs');
const exampleFolder = './examples/';

// 异步调用
fs.readdir(exampleFolder, (err, files) => {
  files.forEach(file => {
    console.log(file);
  });
});
// 同步调用
fs.readdirSync(exampleFolder).forEach(file => {
  console.log(file);
});

但指定的目录下,除了文件,也可能存在文件夹,这就要求需要通过递归循环遍历:如果判定为文件夹,则继续往文件夹内部走,直到该目录只有文件,而没有文件夹才停止。只是一个目录,同步获取其内容,这很容易;但多重嵌套的文件夹(目录),因为不知道何时能遍历完毕,想同步拿到结果,传统的递归遍历,是不足以完成。这就需要借助已经使用非常高频的 async/await 函数。

下面是基于 Deno 以及 Node.js 的两种实现;原理相同,都是基于 TypeScript 编写;只不过调用 API 不同;考虑到以 Deno 语言编写方式,更方便运行,也是未来新趋势,就将其写在前面;您只需安装下 Deno,运行如下命令即可:

Deno 同步获取指定目录下所有文件

deno run --unstable --allow-net --allow-read deep-loop-travesal.ts
/**
 * @desc 深度循环遍历,检索指定目录下所有 .js .ts 文件,并 push 到指定数组;
 * @param directory - 指定目录
 * @param filePathArr - 指定数组
 */
import * as path from "https://deno.land/std/path/mod.ts";
const deepLoopTraversal = async (directory: string, filePathArr: any[]) => {
  for await (const dirEntry of Deno.readDir(directory)) {
    const filename = dirEntry.name
    console.log(filename)
    const filePath = path.join(directory, filename);
    const stats = Deno.statSync(filePath);
    if (stats.isDirectory) {
      await deepLoopTraversal(filePath, filePathArr)
    } else {
      const isFile = stats.isFile;
      const extname = isFile ? path.extname(filePath) : '';
      if (extname === '.js' || extname === '.ts') {
        filePathArr.push(filePath)
      }
    }
  }
}

const main = async () => {
  const filePathArr: Array<string> = []
  await deepLoopTraversal(Deno.cwd(),  filePathArr)
  console.log(`您指定的目录下,是 .js 或 .ts 文件,有以下内容:`)
  console.log(filePathArr)
}

main()

这里有必要申明的是,此处的 for 循环遍历,是不能够使用 forEach 等方法的;这具体缘由,看下 forEach 的方法说明,就了然了:它接收第一个参数,是 callback,即便对它进行 async,但在外层,却是不知何里面是何时遍历完成,这就导致了不同步,从而引起问题。如果您想使用,可以看 StackoverFlow 上大家给出的一些办法:Using async/await with a forEach loop

Node.js 同步获取指定目录下所有文件

以下为 Node.js 写法,启动用到了 fs-extra npm 依赖,需要手动安装下。

/**
 * @desc 深度循环遍历,检索指定目录下所有 .js .ts 文件,并 push 到指定数组;
 * @param directory - 指定目录
 * @param filePathArr - 指定数组
 */
import * as path from 'path';
const fse = require('fs-extra');
const deepLoopTraversal = async (directory: string, filePathArr: any[]) => {
  const filesList = await fse.readdir(directory);
  for (let i = 0, len = filesList.length; i < len; i++) {
    const filename = filesList[i];
    const filePath = path.join(directory, filename);
    const stats = await fse.stat(filePath);
    if (stats.isDirectory()) {
      await deepLoopTraversal(filePath, filePathArr);
    } else {
      const isFile = stats.isFile();
      const extname = isFile ? path.extname(filePath) : '';
      if (extname === '.js' || extname === '.ts') {
        filePathArr.push(filePath)
      }
    }
  }
}

const main = async () => {
  const filePathArr: Array<string> = []
  await deepLoopTraversal(process.cwd(),  filePathArr)
  console.log(`您指定的目录下,是 .js 或 .ts 文件,有以下内容:`)
  console.log(filePathArr)
}

main()

实例说明

建造「测试目录」如下,对其进行运行如上脚本,将会得到什么结果呢?

.
├── a-dir
│   ├── c-file.js
│   └── d-dir
│       └── e-file.js
├── b-file.js
└── deep-loop-travesal.ts

运行脚本,将得到如下结果(因为目录首字母、与文件首字母,决定了 readDir 得到的数组内容顺序,从而使得有以下结果):

[
  ./~yun/a-dir/c-file.js",
  ./~yun/a-dir/d-dir/e-file.js",
  ./~yun/b-file.js",
  ./~yun/deep-loop-travesal.ts"
]

问题思考

这里有个问题,需要思考下;上面所得到文件数组,顺序由每层文件名、文件夹名所决定。如果想要以从外至内的顺序,得到最终的结果数组,那么该如何处理呢?Promise.all 是解决这个问题的一剂良方。如果判定是文件夹,则将对它的递归循环,放置于一个数组(promiseList),后面将该数组传递给 Promise.all 方法予以调用,问题迎刃而解(目录中内容,将会延迟遍历)。

Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个 Promise 实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个 Promise 的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息。

/**
 * @file deep-loop-travesal.ts
 * @desc 深度循环遍历,检索指定目录下所有 .js .ts 文件,并 push 到指定数组;
 * @param directory - 指定目录
 * @param filePathArr - 指定数组
 */
import * as path from "https://deno.land/std/path/mod.ts";
const deepLoopTraversal = async (directory: string, filePathArr: any[]) => {
  const promiseList: Promise<any>[] = [];
  for await (const dirEntry of Deno.readDir(directory)) {
    const filename = dirEntry.name
    const filePath = path.join(directory, filename);
    const stats = Deno.statSync(filePath);
    if (stats.isDirectory) {
      promiseList.push(deepLoopTraversal(filePath, filePathArr));
    } else {
      const isFile = stats.isFile;
      const extname = isFile ? path.extname(filePath) : '';
      if (extname === '.js' || extname === '.ts') {
        filePathArr.push(filePath)
      }
    }
  }
  if (promiseList.length) {
    await Promise.all(promiseList);
  }
}

const main = async () => {
  const filePathArr: Array<string> = []
  await deepLoopTraversal(Deno.cwd(),  filePathArr)
  console.log(`您指定的目录下,是 .js 或 .ts 文件,有以下内容:`)
  console.log(filePathArr)
}

main()

依旧是上面的「测试目录」,运行如上改造后的脚本,将得到如下结果,结果符合预期;

[
  ~/yun/b-file.js",
  ~/yun/deep-loop-travesal.ts",
  ~/yun/a-dir/c-file.js",
  ~/yun/a-dir/d-dir/e-file.js"
]

以上,便是关于“Node.js/Deno,如何同步获取指定目录下所有文件“问题的一点,如有谬误,欢请斧正。


倾城之链作为一个开放平台,旨在云集全球优秀网站,探索互联网中更广阔的世界;在这里,你可以轻松发现、学习、分享更多有用或有趣的事物。

您可能感兴趣的文章