Node.js で 再帰的に ディレクトリ を作成する 方法

3 件のコメント

npm にあがっているソースが Promise に対応していなかったので、 Promise 対応版を作ってみました。 こういうのは本当は pull request するものなんでしょうが… 名前がいろいろ被っていてアップロード難しかったのでこのままここに貼り付けておきます。

ソースコード(mkdir-r)

ソースコードは以下に記載のものですべてなので、以下のコードをコピペすれば使えます。

mkdir-r.js

var fs = require("fs");
var path = require("path");

/**
 * Make specified directory recursively.
 * @param {string} url Directory or file path.
 * @param {number} [mode] Accessibility.
 */
var makeDirectoryRecursivelySync = function (url, mode) {
  var dir = "";
  var list = path.normalize(url).split(path.sep);
  var max = list.length - 1;

  dir = list.slice(0, max).join(path.sep);
  try {
    fs.mkdirSync(dir, mode);
  } catch (error) {
    if (error.code === "ENOENT") {
      makeDirectoryRecursivelySync(list.slice(0, max - 1).join(path.sep) + path.sep, mode);
      fs.mkdirSync(dir, mode);
    } else {
      throw error;
    }
  }
};

/**
 * Asyncronouse make specified directory recursively.
 * @param {string} url Directory or file path.
 * @param {number} [mode] Accesibility.
 * @param {function} callback Callback function which called when directories creation is completed.
 */
var makeDirectoryRecursivelyAsync = function (url, mode, callback) {
  var main = function (url, mode, callback) {
    if (typeof (mode) === "function") {
      callback = mode;
      mode = undefined;
    }

    var dir = "";
    var list = path.normalize(url).split(path.sep);
    var max = list.length - 1;

    dir = list.slice(0, max).join(path.sep);
    fs.mkdir(dir, mode, (error) => {
      if (error) {
        if (error.code === "ENOENT") {
          makeDirectoryRecursivelyAsync(list.slice(0, max - 1).join(path.sep) + path.sep, mode, (error) => {
            fs.mkdir(dir, mode, callback);
          });
        } else {
          throw error;
        }
      } else {
        callback && callback(null);
      }
    });
  };

  if (callback) {
    main(url, mode, callback);
  } else {
    return new Promise((resolve, reject) => {
      main(url, mode, (error) => {
        if (error) {
          reject(error);
        } else {
          resolve(null);
        }
      })
    });
  }
};

module.exports = makeDirectoryRecursivelyAsync;
module.exports.sync = makeDirectoryRecursivelySync;
module.exports.async = makeDirectoryRecursivelyAsync;

API ドキュメント

mkdir-r(url, mode, callback)

非同期で指定されたディレクトリまで再帰的に作成します。

引数

url
再帰的に作成したいディレクトリパスを指定します。 最後のセパレータ / までをディレクトリとして認識します。 例えば "/a/b/c" であれば c はファイルとして認識するので、作成されるディレクトリは a および b になります。
mode
作成するディレクトリのアクセス権を設定します。
callback(error)
作成が完了したとき呼び出されます。 引数は エラーオブジェクト error のみです。

戻り値

Promise
コールバックが設定された Promise オブジェクト を戻します。

使い方

単純に作成するだけであれば以下のようなコードになります。

var mkdir = require("./mkdir-r2.js");

mkdir("/data/write1/hoge/foo/bar.txt");

作成した後に何かしたい場合はコールバックを指定します。

var mkdir = require("./mkdir-r2.js");

mkdir("/data/write1/hoge/foo/bar.txt", (error) => {
  if (error) {
    console.log(error.message);
    return;
  }
  console.log("complete");
});
  1. 2回実行すると「EEXIST: file already exists, mkdir」エラーが発生します。サーバサイドでは非同期に実行される事が多くフォルダの存在チェック後に実行しても並行に処理されている時は処理が重なることがあります。

    非同期版では process.nextTick で遅延実行されている様ですが、非同期処理の中で同期処理(sync)が呼ばれていますので、そこで処理が一時的にブロックされ、サーバサイドで実行されることの多い Node.js のスタイルとしては、よくないです。

    また、後半のサンプルでは、mkdir-r という変数が使われていますが、ハイフンは変数名には使用できません。

    返信削除
    返信
    1. >西澤さん
      コメントありがとうございます。

      > mkdir-r という変数が使われていますが、ハイフンは変数名には使用できません。
      ご指摘ありがとうございます。
      すっかり失念しておりました(手元のサンプル実行した際のコードだと mkdir でやっていました…)。
      サンプル修正しました。

      > 2回実行すると「EEXIST: file already exists, mkdir」エラーが発生します。
      こちらについてですが、私の環境(Windows 10)で、複数回実行時にエラーが発生することを再現させようとしてみたのですが、
      ご記載いただいた「EEXIST: file already exists, mkdir」エラーを発生させることができませんでした。
      可能であれば、具体的な再現方法をご教示いただけないでしょうか。

      当方で再現実行し具体的な方法は Windows 10 、Node.js v6.9.2 環境で
      「上記のメソッドを利用して同一ディレクトリを作成する処理」を無限ループ実行するバッチを10多重で動作させながら
      さらにVisual Studio Code でデバッグ実行を10回実行してみました。
      途中、最後のフォルダ名が異なるようにしたりもしてみましたが再現できませんでした。

      何か観点が私に抜けているような気がするので、ご回答いただけると幸いです。

      削除
    2. >西澤さん
      かなり時間が空いてしまいましたが…
      「Asyncが非同期IOになっていない」というご指摘だったことに
      当時気づいていませんでした。

      大変失礼しました。また、ご指摘ありがとうございました。
      コードは非同期IOを利用するよう修正しました。

      また何かおかしな点ありましたらご指摘いただけると嬉しいです!

      削除