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

前端开发 Mar 1, 2021

在基于 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 promiseList: Promise<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,如何同步获取指定目录下所有文件“问题的一点,如有谬误,欢请斧正。


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

小程序码 - 倾城之链

您可能感兴趣的文章

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.