filterのコールバック関数内で非同期処理を行いたい

typescriptのfilterのコールバック関数で非同期処理を行いたいときに、少しハマったのでメモ。

下の例では、src/にあるディレクトリのうち、空でないものを取り出している。
つまり、src/の子の位置にあるディレクトリのうちaaa/ccc/を取り出している。


主なディレクトリ構成(簡単のためpackage.jsonなどは除く)

.
├──main.ts
└── src
    ├── aaa
    │    └── README.md
    ├── bbb
    └── ccc
          └── README.md


ソースコード

import fs from 'node:fs/promises';
import path from 'node:path';

import fg from 'fast-glob';

// run with `yarn ts-node main.ts`

void main();

async function main() {
  const sourceDirents = await fs.readdir(path.join('src'), { encoding: 'utf8', withFileTypes: true });

  console.log('sourceDirents', sourceDirents);

  // NG
  // const filteredSourceDirents = sourceDirents.filter(async (d) => {
  //   if (!d.isDirectory()) return false;
  //   const dir = await fg(path.join('src', d.name, '*'));
  //   return dir.length > 0;
  // });

  // OK
  const filteredSourceDirents = await Promise.all(
    sourceDirents.map(async (d) => {
      if (!d.isDirectory()) return false;
      const dir = await fg(path.join('src', d.name, '*'));
      return dir.length > 0;
    })
  ).then((res) => sourceDirents.filter((_, i) => res[i]));

  console.log('filteredSourceDirents', filteredSourceDirents);
}


実行結果

sourceDirents [
  Dirent { name: 'aaa', [Symbol(type)]: 2 },
  Dirent { name: 'bbb', [Symbol(type)]: 2 },
  Dirent { name: 'ccc', [Symbol(type)]: 2 }
]
filteredSourceDirents [
  Dirent { name: 'aaa', [Symbol(type)]: 2 },
  Dirent { name: 'ccc', [Symbol(type)]: 2 }
]

実行結果から、aaacccディレクトリを取り出すことができているのが確認できる。

参考

[Node] 高階関数内での非同期処理(async/await)をどう書くか | Today's Commit