Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

Node.jsのFile操作まとめ

ファイル読み込み処理を書くたびにググっているので、まとめてみました。書き込みについては、読み込みとほぼ同じなので割愛します。

追記: fs/promises が最善

(2023/11/29 追記)

fs/promises というものが Node v10.0.0 で追加されていました。こちらの中にある readFile は始めから async になっています。

const readFile = require('fs/promises').readFile;

const path = './hoge.txt';
const data = await readFile(path, 'utf8'); // これでhoge.txtの内容が読み出せる!

これがファイルを読み込む一番カンタンな方法です。

以下は以前書いたものになります。

async/await + util.promisify (最適解っぽい)

async/awaitが使える環境なら(おそらく)これが最強です

const readFile = require('util').promisify(require('fs').readFile);

const path = './hoge.txt';
const data = await readFile(path, 'utf8'); // これでhoge.txtの内容が読み出せる!
メリット
短く可読性が高い
例外処理も行うことができる
処理をブロックしない

async/awaitを使うパターン

const fs = require('fs');

const readFile = path => new Promise((resolve, reject) => {
  fs.readFile(path, 'utf8', (err, data) => {
    if (err) { reject(err); return; }
    resolve(data);
  });
});

// エラーを握りつぶしてもいいならreadFile()は次のようにできます
// const readFile = path => new Promise(r =>
//   fs.readFile(path, 'utf8', (_, data) => r(data)));

const path = './hoge.txt';
const data = await readFile(path);
メリット
処理をブロックしない
かなり読みやすい
例外処理もできる

async/awaitは使えるなら積極的に使ったほうが良いと思います。

async/await なしパターン

const fs = require('fs');
const path = './hoge.txt';

fs.readFile(path, 'utf8', (err, data) => {
  if (err) throw err;
  // something...
})
メリット
処理をブロックしない

このパターンは処理をブロックしないメリットはありますが、処理部分(something...)が中に埋まっていて、可読性が落ちています。

同期的に操作するパターン

const fs = require('fs');
const path = './hoge.txt';

const data = fs.readFileSync(path, 'utf8');
メリットデメリット
シンプルに書けるパフォーマンスが低下する
可読性は高い

このパターンはパフォーマンスがとても落ちるので、書き捨てる時以外はなるべく使用しないほうがいいです。

readStreamを使うパターン

const fs = require('fs');
const path = './hoge.txt';

const rs = fs.createReadStream(path, {highWaterMark: 10}); //bufferSizeではなくhighWaterMarkになった
rs.setEncoding('utf8');

rs.on('data', data => console.log(data));

これは上3つとは異なるユースケースなので、メリット・デメリットを語ってもあまり意味がないですね。

このパターンは、大きいデータを読み込んで順次処理するのに向いています。

fs.openを使うパターン

これはめったに使わないと思います・・・いつ使うのでしょうか。

const fs = require('fs');
const path = './hoge.txt';

fs.open(path, 'r', (err, fd) => {
  fs.fstat(fd, (err, stats) => {
    const bufferSize = stats.size,
      buffer = new Buffer(bufferSize); // ここにデータが蓄積されていく
    let chunkSize = 5, // チャンクサイズ
      bytesRead = 0; // これまでに読んできたバイト数
    while (bytesRead < bufferSize) {
      if (bytesRead + chunkSize > bufferSize) {
        chunkSize = bufferSize - bytesRead;
      }
      fs.read(fd, buffer, bytesRead, chunkSize, bytesRead, (err, bytesRead, buffer) => { });
      bytesRead += chunkSize; // いままで読み込んだバイト数を更新
    }
    console.log(buffer.toString('utf8')); // 読み終わった
    fs.close(fd, err => console.log(err));
  });
});