fs / promises и fs.Dirent
Рекурсия - это функциональное наследие, поэтому использование ее в функциональном стиле дает наилучшие результаты. Вот эффективная программа dirs
, использующая быстрые fs.Dirent объекты Node и модуль fs/promises
. Такой подход позволяет пропустить бесполезные вызовы fs.exist
или fs.stat
на каждом пути -
import { readdir } from "fs/promises"
import { join } from "path"
async function* dirs (path = ".")
{ yield path
for (const dirent of await readdir(path, { withFileTypes: true }))
if (dirent.isDirectory())
yield* dirs(join(path, dirent.name))
}
async function* empty () {}
async function toArray (iter = empty())
{ let r = []
for await (const x of iter)
r.push(x)
return r
}
toArray(dirs(".")).then(console.log, console.error)
Давайте возьмем несколько файлов, чтобы мы могли видеть dirs
работающие -
$ yarn add immutable # (just some example package)
$ node main.js
[
'.',
'node_modules',
'node_modules/immutable',
'node_modules/immutable/contrib',
'node_modules/immutable/contrib/cursor',
'node_modules/immutable/contrib/cursor/__tests__',
'node_modules/immutable/dist'
]
asyn c генераторы
И поскольку мы используем asyn c генераторы , мы можем интуитивно остановить итерацию, как только найден соответствующий файл -
import { readdir } from "fs/promises"
import { join, basename } from "path"
async function* dirs // ...
async function* empty // ...
async function toArray // ...
async function search (iter = empty(), test = _ => false)
{ for await (const p of iter)
if (test(p))
return p // <-- iteration stops here
}
search(dirs("."), path => basename(path) === "contrib") // <-- search for contrib
.then(console.log, console.error)
search(dirs("."), path => basename(path) === "foobar") // <-- search for foobar
.then(console.log, console.error)
$ node main.js
node_modules/immutable/contrib # ("contrib" found)
undefined # ("foobar" not found)
изобретайте для себя
Выше search
- это функция высшего порядка, например Array.prototype.find
. Мы могли бы написать searchByName
, что, вероятно, более удобно использовать -
import // ...
async function* dirs // ...
async function* empty // ...
async function toArray // ...
async function search // ...
async function searchByName (iter = empty(), query = "")
{ return search(iter, p => basename(p) === query) }
searchByName(dirs("."), "contrib")
.then(console.log, console.error)
searchByName(dirs("."), "foobar")
.then(console.log, console.error)
Вывод такой же -
$ node main.js
node_modules/immutable/contrib # ("contrib" found)
undefined # ("foobar" not found)
сделать его модулем
Практика, которой уделяется недостаточно внимания. Создавая модуль , мы создаем место для разделения задач и не позволяем сложности перегрузить остальную часть нашей программы -
// FsExtensions.js
import { readdir } from "fs/promises" // <-- import only what you need
import { join, basename } from "path"
async function* dirs // ...
async function* empty // ...
async function search // ...
async function searchByName // ...
async function toArray // ...
// ...
export { dirs, search, searchByName, toArray } // <-- you control what to export
// Main.js
import { dirs, searchByName } from './FsExtensions' // <-- import only what's used
searchByName(dirs("."), "contrib")
.then(console.log, console.error)
searchByName(dirs("."), "foobar")
.then(console.log, console.error)
ограничение глубины
dirs
реализуется с помощью рекурсивного генератора. Мы можем легко ограничить глубину рекурсии, добавив параметр в нашу функцию. Я использую значение по умолчанию Infinity
, но вы можете выбрать все, что захотите -
async function* dirs (path = "."<b>, depth = Infinity)</b>
{ <b>if (depth < 1) return</b> // stop if depth limit is reached
yield path
for (const dirent of await readdir(path, { withFileTypes: true }))
if (dirent.isDirectory())
yield* dirs(join(path, dirent.name)<b>, depth - 1</b>)
}
Когда dirs
вызывается со вторым аргументом, глубина рекурсии ограничена -
toArray(dirs(".", 1)).then(console.log, console.error)
// [ '.' ]
toArray(dirs(".", 2)).then(console.log, console.error)
// [ '.', 'node_modules' ]
toArray(dirs(".", 3)).then(console.log, console.error)
// [ '.', 'node_modules', 'node_modules/immutable' ]
toArray(dirs(".", 4)).then(console.log, console.error)
// [ '.',
// 'node_modules',
// 'node_modules/immutable',
// 'node_modules/immutable/contrib',
// 'node_modules/immutable/dist'
// ]
searchByName(dirs(".", 1), "contrib").then(console.log, console.error)
// undefined
searchByName(dirs(".", 2), "contrib").then(console.log, console.error)
// undefined
searchByName(dirs(".", 3), "contrib").then(console.log, console.error)
// undefined
searchByName(dirs(".", 4), "contrib").then(console.log, console.error)
// node_modules/immutable/contrib
searchByName(dirs("."), "contrib").then(console.log, console.error)
// node_modules/immutable/contrib
searchByName(dirs("."), "foobar").then(console.log, console.error)
// undefined