fs/promises and fs.Dirent
Here's an efficient, non-blocking ls
program using Node's fast fs.Dirent objects and fs/promises module. This approach allows you to skip wasteful fs.exist
or fs.stat
calls on every path -
// main.js
import { readdir } from "fs/promises"
import { join } from "path"
async function* ls (path = ".")
{ yield path
for (const dirent of await readdir(path, { withFileTypes: true }))
if (dirent.isDirectory())
yield* ls(join(path, dirent.name))
else
yield 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(ls(".")).then(console.log, console.error)
Let's get some sample files so we can see ls
working -
$ yarn add immutable # (just some example package)
$ node main.js
[
'.',
'main.js',
'node_modules',
'node_modules/.yarn-integrity',
'node_modules/immutable',
'node_modules/immutable/LICENSE',
'node_modules/immutable/README.md',
'node_modules/immutable/contrib',
'node_modules/immutable/contrib/cursor',
'node_modules/immutable/contrib/cursor/README.md',
'node_modules/immutable/contrib/cursor/__tests__',
'node_modules/immutable/contrib/cursor/__tests__/Cursor.ts.skip',
'node_modules/immutable/contrib/cursor/index.d.ts',
'node_modules/immutable/contrib/cursor/index.js',
'node_modules/immutable/dist',
'node_modules/immutable/dist/immutable-nonambient.d.ts',
'node_modules/immutable/dist/immutable.d.ts',
'node_modules/immutable/dist/immutable.es.js',
'node_modules/immutable/dist/immutable.js',
'node_modules/immutable/dist/immutable.js.flow',
'node_modules/immutable/dist/immutable.min.js',
'node_modules/immutable/package.json',
'package.json',
'yarn.lock'
]
And we want to filter
just .js
files -
import { extname } from "path"
async function* filter(iter = empty(), test = x => x)
{ for await (const x of iter)
if (Boolean(test(x)))
yield x
}
const lsJs = (path = ".") =>
filter // <- filtered stream
( ls(path) // <- input stream
, p => extname(p) === ".js" // <- filter predicate
)
toArray(lsJs(".")).then(console.log, console.error)
// => ...
[
'main.js',
'node_modules/immutable/contrib/cursor/index.js',
'node_modules/immutable/dist/immutable.es.js',
'node_modules/immutable/dist/immutable.js',
'node_modules/immutable/dist/immutable.min.js'
]
A more generic lsExt
allows us to filter by any extension. We are not limited to only .js
-
const lsExt = (path = ".", ext) =>
ext
? filter(ls(path), p => extname(p) === ext)
: ls(path)
toArray(lsExt(".", ".json")).then(console.log, console.error)
// => ...
[
'node_modules/immutable/package.json',
'package.json'
]
It's hard to debug big functions that take on too many responsibilities. Breaking the problem down made our program easier to write and our functions are highly reusable too. The next step would be to define our set of features in a module. For added explanation and other ways to leverage async generators, see this Q&A.