deanWombourne
deanWombourne

Reputation: 38475

Is there a better way to write this recursing method in typescript

I'm trying to write a method to find all the files in a folder, including subfolders. It's pretty simple to write using fs.readdirSync, but I'm trying to write a version which doesn't block. (i.e. uses fs.readdir).

I've got a version which works, but it's not pretty. Can someone who has a bit more experience with node see if there is a nicer way to write this? I can see a few other places in my codebase where I can apply this pattern so it would be nice to have a cleaner version!

  private static findFilesFromFolder(folder: string): Promise<string[]> {
    let lstat = util.promisify(fs.lstat)
    let readdir = util.promisify(fs.readdir)

    // Read the initial folder
    let files = readdir(folder)

      // Join the folder name to the file name to make it absolute
      .then(files => files.map(file => path.join(folder, file)))

      // Get the stats for each file (also as a promise)
      .then(files =>
        Promise.all(files.map(file =>
          lstat(file).then(stats => { return { file: file, stats: stats } })
        ))
      )

      // If the file is a folder, recurse. Otherwise just return the file itself.
      .then(info =>
        Promise.all(info.map(info => {
          if (info.stats.isDirectory()) {
            return this.findFilesFromFolder(info.file)
          } else {
            return Promise.resolve([info.file])
          }
        }
      )))

      // Do sume munging of the types - convert Promise<string[][]> to Promise<string[]>
    .then(nested => Array.prototype.concat.apply([], nested) as string[])

    return files
  }

Upvotes: 2

Views: 78

Answers (1)

Bergi
Bergi

Reputation: 664787

I'd do a few things to make this cleaner:

  • move the recursion base case from within the loop to the top level
  • use async/await syntax
  • use const instead of let

Also put the promisify calls to where you are importing fs

const lstat = util.promisify(fs.lstat)
const readdir = util.promisify(fs.readdir)

…
private static async findFilesFromPath(folder: string): Promise<string[]> {
  const stats = await lstat(folder);
  // If the file is a folder, recurse. Otherwise just return the file itself.
  if (stats.isDirectory()) {
    // Read the initial folder
    const files = await readdir(folder);
    // Join the folder name to the file name to make it absolute
    const paths = files.map(file => path.join(folder, file))
    const nested = await Promise.all(paths.map(p => this.findFilesFromPath(p)))
    // Do sume munging of the types - convert string[][] string[]
    return Array.prototype.concat.apply([], nested) as string[];
  } else {
    return [folder];
  }
}

Upvotes: 3

Related Questions