node.js ファイル一覧の取得(トップ/サブディレクトリ全て、同期/非同期 対応)

# はじめに

node.js ではローカルファイルにアクセスできます。

ファイル一覧を取得しようとしたのですが、readdir と readdirSync では、サブディレクトリ全てのファイル一覧を取得できなかったので、作ってみました。

# 同期/非同期について、工夫したところ。

同期処理は、全て処理が終わってから関数が戻ります。
非同期処理は、関数が戻るときには処理は終わってなくて、処理はコールバック関数で定義して実行させます。

readdir と readdirSync は 手軽に使えますから、同期処理、非同期処理のよいサンプルになりました。

サブディレクトリ内全てのファイル一覧というのは、再帰処理で書くというのは一般的なプログラマならよくやるネタかと思いますが、これに非同期処理が入ってくると制御に工夫が必要でしたので、そこのところなど参考になるのではないでしょうか。


# ソースコード

同期関数として、readTopDirSync/readSubDirSync
非同期処理として使うための関数として、readTopDir/readSubDir という名前で作りました。

普通は使いやすい同期関数を使えばいいと思います。

ソースが長いですが次のとおりです。
一番したの方に、使い方ソースがあります。

```js:readdir_sample.js
'use strict';
const fs = require('fs');
const path = require('path');

  //----------------------------------------
  //・トップフォルダのみのファイルアイテム列挙
  //----------------------------------------
  //  ・同期
  //  ・戻り値にはフォルダフルパスが入る
  //  ・戻り値以外は fs.readdir とほぼ同じ
  //----------------------------------------
  const readTopDirSync = (folderPath) => {
    const result = fs.readdirSync(folderPath);
    return result.map((itemName) => {
      return path.join(folderPath, itemName);
    });
  };

  //----------------------------------------
  //・サブフォルダ下位すべてのファイルアイテム列挙
  //----------------------------------------
  //  ・同期
  //  ・戻り値にはフォルダフルパスが入る
  //----------------------------------------
  const readSubDirSync = (folderPath) => {
    let result = [];
    const readTopDirSync = ((folderPath) => {
      let items = fs.readdirSync(folderPath);
      items = items.map((itemName) => {
        return path.join(folderPath, itemName);
      });
      items.forEach((itemPath) => {
        result.push(itemPath);
        if (fs.statSync(itemPath).isDirectory()) {
          readTopDirSync(itemPath);
          //再帰処理
        }
      });
    });
    readTopDirSync(folderPath);
    return result;
  };

  //----------------------------------------
  //・トップフォルダのみのファイルアイテム列挙
  //----------------------------------------
  //  ・非同期
  //  ・errorFunc:  エラー時の処理
  //  ・itemFunc:   ファイル/ディレクトリ列挙時の処理
  //  ・finishFunc: 処理終了時に一覧を取得する時の処理
  //----------------------------------------
  const readTopDir = (folderPath, errorFunc, itemFunc, finishFunc) => {

    fs.readdir(folderPath, (err, items) => {
      if (err) {
        if (errorFunc) {
          errorFunc(err);
        }
      }

      items = items.map((itemName) => {
        return path.join(folderPath, itemName);
      });

      if (itemFunc) {
        items.forEach((itemPath) => {
          itemFunc(itemPath);
        });
      }

      if (finishFunc) {
        finishFunc(items);
      }

    });
  };

  //----------------------------------------
  //・サブフォルダ下位すべてのファイルアイテム列挙
  //----------------------------------------
  //  ・非同期
  //  ・errorFunc:  エラー時の処理
  //  ・itemFunc:   ファイル/ディレクトリ列挙時の処理
  //  ・finishFunc: 処理終了時に一覧を取得する時の処理
  //----------------------------------------
  const readSubDir = (folderPath, errorFunc, itemFunc, finishFunc) => {

    let result = [];
    let execCounter = 0;
    const readTopDir = (folderPath) => {
      execCounter += 1;
      fs.readdir(folderPath, function(err, items) {
        if (err) {
          if (errorFunc) {
            errorFunc(err);
          }
        }

        items = items.map((itemName) => {
          return path.join(folderPath, itemName);
        });

        items.forEach((itemPath) => {
          result.push(itemPath);
          if (itemFunc) {
            itemFunc(itemPath);
          }
          if (fs.statSync(itemPath).isDirectory()) {
            //フォルダなら再帰呼び出し
            readTopDir(itemPath);
          }
        });
        execCounter -= 1;
        if (execCounter === 0) {
          if (finishFunc) {
            finishFunc(result);
          }
        }
      });
    }
    readTopDir(folderPath);
  };


let folderPath = process.cwd();
folderPath = path.resolve(folderPath, '../');

//トップディレクトリのみの列挙
var items = readTopDirSync(folderPath);
items.forEach((itemPath) => {
  console.log(itemPath)
});
console.log('Finish readTopDirSync');

//サブディレクトリの列挙
var items = readSubDirSync(folderPath);
items.forEach((itemPath) => {
  console.log(itemPath)
});

console.log('Finish readSubDirSync');

//トップディレクトリのみの列挙 非同期
var items = readTopDir(folderPath,
  (err) => {throw err},
  (itemPath) => {
    console.log(itemPath);
  },
  (items) => {
    console.log(items.length.toString());
  }
);
console.log('Finish readTopDir');

//サブディレクトリの列挙 非同期
var items = readSubDir(folderPath,
  (err) => {throw err},
  (itemPath) => {
    console.log(itemPath);
  },
  (items) => {
    console.log(items.length.toString());
  }
);

console.log('Finish readSubDir');

```

# 実行方法

次のようにコマンドを入力してください。
node.js が動く環境なら動くはずです。

```
node readdir_sample.js
```

動かしてみたら、 同期関数と非同期関数がどのようなタイミングで動くか、よくわか

# ライブラリ

自作のライブラリに組み込みました。

かなり広範囲な環境(Browser/node.js/GAS SpreadSheet/WSH JSCript)で動作する標準的なライブラリを作っています。広範囲環境の対応ライブラリの作り方の参考にもどぞ。

stsLib.js/stslib_node.js at master · standard-software/stsLib.js · GitHub
https://github.com/standard-software/stsLib.js/blob/master/Source/stsLib.js/stslib_node.js