Node.js 如何同步获取指定目录下所有文件?
在基于 Node.js 编写程序,时常会遇到类似这样的需求:“列出(遍历)目录下的所有文件,包括子目录“。如何实现这一点呢,很显然可以使用一些已写好的工具库,如 node-rd、node-walk、glob 等;但她们或多或少,都有些弊端,不方便使用;那么手动写一个方法,来实现“同步获取指定目录下所有文件“呢?本篇文章,即同大家一起探讨下这个问题。
如果要获得指定目录下文件,您可以使用 fs.readdir
或 fs.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,如何同步获取指定目录下所有文件“问题的一点,如有谬误,欢请斧正。
倾城之链作为一个开放平台,旨在云集全球优秀网站,探索互联网中更广阔的世界;在这里,你可以轻松发现、学习、分享更多有用或有趣的事物。