Is this valid syntax for node/promisify async function?

Issue

I accidentally typed await(await stat(content... and it worked. Not sure if this is valid syntax, or there is a better way to do it? I’m trying to read all the files that are directories and do not match my regex.

const fs = require('fs')
const path = require('path')
const content = path.resolve('.') + '/docs' + '/'
const util = require('util');
const stat = util.promisify(fs.stat)
const readDir = util.promisify(fs.readdir)
const directories = 'docs/';
const exclude = new RegExp(/^(adir|\.somedir)/,'i');
let newFiles = {}

async function main(){
    const ls = await readDir(directories)
    console.log('starting....');
    let newArray = []
     for (let index = 0; index < ls.length; index++) {
               let x =  await (await stat(content + ls[index])).isDirectory()
               let file = ls[index]
               if (x && !(exclude.test(file))){newArray.push(file)}
               console.log('x is ',x);
        }   
    console.log('new filtered array: ', newArray);
}

Solution

ls

My advice would be not to put all of your eggs in one basket. We can write an ultra fast ls function using Node’s fs.Dirent objects and bypass the need for a slow fs.stat call on each file –

// fsext.js

import { readdir } from "fs/promises"
import { join } from "path"

async function* ls (path = ".")
{ yield { dir: path }
  for (const dirent of await readdir(path, { withFileTypes: true }))
    if (dirent.isDirectory())
      yield *ls(join(path, dirent.name))
    else
      yield { file: join(path, dirent.name) }
}

async function toArray (iter)
{ const r = []
  for await (const v of iter)
    r.push(v)
  return r
}

export { ls, toArray }
// main.js

import { ls, toArray } from "./fsext.js"

toArray(ls("./node_modules")).then(console.log, console.error)

To test it out, let’s add some popular npm packages so we have a large hierarchy to test our our program. We’ll install the lot and count the number of directories and files –

$ npm install async chalk commander debug express immutable lodash moment prop-types react react-dom request webpack

$ find ./node_modules | wc -l
5453

Now let’s run our program and time it –

$ time node main.js
[
  { dir: './node_modules' },
  { dir: 'node_modules/.bin' },
  { file: 'node_modules/.bin/acorn' },
  { file: 'node_modules/.bin/browserslist' },
  { file: 'node_modules/.bin/loose-envify' },
  { file: 'node_modules/.bin/mime' },
  { file: 'node_modules/.bin/sshpk-conv' },
  { file: 'node_modules/.bin/sshpk-sign' },
  { file: 'node_modules/.bin/sshpk-verify' },
  { file: 'node_modules/.bin/terser' },
  { file: 'node_modules/.bin/uuid' },
  { file: 'node_modules/.bin/webpack' },
  { file: 'node_modules/.package-lock.json' },
  { dir: 'node_modules/@types' },
  { dir: 'node_modules/@types/eslint' },
  { file: 'node_modules/@types/eslint/LICENSE' },
  { file: 'node_modules/@types/eslint/README.md' },
  { file: 'node_modules/@types/eslint/helpers.d.ts' },
  { file: 'node_modules/@types/eslint/index.d.ts' },
  { dir: 'node_modules/@types/eslint/lib' },
   ... 5433 more items
]
node main.js  0.09s user 0.02s system 116% cpu 0.099 total

dirs

If we only want directories, we can write dirs as a simple specialization of our generic ls

// fsext.js (continued)

async function* dirs (path)
{ for await (const f of ls(path))
    if (f.dir)
      yield f.dir
}
$ find ./node_modules -type d | wc -l
457

Now compare it against our program

// main.js

import { dirs, toArray } from "./fsext.js"

toArray(dirs("./node_modules")).then(console.log, console.error)
$ time node.main.js
[
  './node_modules',
  'node_modules/.bin',
  'node_modules/@types',
  'node_modules/@types/eslint',
  'node_modules/@types/eslint/lib',
  'node_modules/@types/eslint/lib/rules',
  'node_modules/@types/eslint/rules',
  'node_modules/@types/eslint-scope',
  'node_modules/@types/estree',
  'node_modules/@types/json-schema',
  'node_modules/@types/node',
  'node_modules/@types/node/assert',
  'node_modules/@types/node/dns',
  'node_modules/@types/node/fs',
  'node_modules/@types/node/stream',
  'node_modules/@types/node/timers',
  'node_modules/@types/node/ts3.6',
  'node_modules/@webassemblyjs',
  'node_modules/@webassemblyjs/ast',
  'node_modules/@webassemblyjs/ast/esm',
  ... 437 more items
]
node main2.js  0.09s user 0.02s system 108% cpu 0.099 total

exclude

If we want to exclude certain directories or files, we can write it generically as well –

// fsext.js (continued)

async function* exclude (iter, test)
{ for await (const v of iter)
    if (Boolean(test(v)))
      continue
    else
      yield v
}
// main.js

import { dirs, exclude, toArray } from "./fsext.js"

toArray(exclude(dirs("./node_modules"), v => /@/.test(v)))
  .then(console.log, console.error)

$ time node main.js
[
  './node_modules',
  'node_modules/.bin',
  'node_modules/accepts',
  'node_modules/acorn',
  'node_modules/acorn/bin',
  'node_modules/acorn/dist',
  'node_modules/ajv',
  'node_modules/ajv/dist',
  'node_modules/ajv/lib',
  'node_modules/ajv/lib/compile',
  'node_modules/ajv/lib/dot',
  'node_modules/ajv/lib/dotjs',
  'node_modules/ajv/lib/refs',
  'node_modules/ajv/scripts',
  'node_modules/ajv-keywords',
  'node_modules/ajv-keywords/keywords',
  'node_modules/ajv-keywords/keywords/dot',
  'node_modules/ajv-keywords/keywords/dotjs',
  'node_modules/ansi-styles',
  'node_modules/array-flatten',
  ... 351 more items
]
node main.js  0.09s user 0.02s system 105% cpu 0.104 total

reorganize

In our file system extensions module, fsext, we wrote two functions that work on any iterables, not just the ls or dirs. I would suggest breaking these out into their own iter module. This type of reorganization helps decouple concerns and maximize code reuse throughout your entire program –

// iter.js

async function* empty () {}

async function* exclude (iter = empty(), test = Boolean)
{ for await (const v of iter)
    if (Boolean(test(v)))
      continue
    else
      yield v
}

async function toArray (iter = empty())
{ const r = []
  for await (const v of iter)
    r.push(v)
  return r
}

export { empty, exclude, toArray }
// fsext.js

import { readdir } from "fs/promises"
import { join } from "path"

async function* ls (path = ".")
{ yield { dir: path }
  for (const dirent of await readdir(path, { withFileTypes: true }))
    if (dirent.isDirectory())
      yield *ls(join(path, dirent.name))
    else
      yield { file: join(path, dirent.name) }
}

async function* dirs (path)
{ for await (const f of ls(path))
    if (f.dir)
      yield f.dir
}

async function* files (path)
{ for await (const f of ls(path))
    if (f.file)
      yield f.file
}

export { ls, dirs, files }
// main.js

import { dirs } from "./fsext.js"
import { exclude, toArray } from "./iter.js"

const somePath = "..."
const someTest = v => ...

toArray(exclude(dirs(somePath), someTest))
  .then(console.log, console.error)

search

Looking for a specific file or folder? Read on in this Q&A to implement search.

Answered By – Mulan

Answer Checked By – Willingham (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.