お役立ち

Node.jsでファイルの読み込みreadFileとreadFileSync

  • このエントリーをはてなブックマークに追加

まよったらreadFileSyncを使う

Node.jsをはじめたばかりで「同期?非同期?」な人は、
とりあえず、fs.readFileSyncを使うのが無難です。
readFileSyncを使うことでJavaやPython,Rubyなど
一般的な言語のファイル読み込みと同じ挙動になります。
下記はファイルを読み込んで、その内容を表示するスクリプトです。

const fs = require('fs');
const msg = fs.readFileSync("hoge.txt", {encoding: "utf-8"});
console.log(msg);

実行すると、ファイルの中身が表示されます。


ほげほげふがふが

Node.js風の書き方readFile

次に非同期処理を生かしたNode.js風の書き方をしてみます。
readFileSyncではなく、fs.readFileを利用してみます。

const fs = require('fs');
let msg = '';
fs.readFile('hoge.txt', 'utf-8', (err, data) => {
  // 例外処理
  if (err) { throw err; }
  msg = data;
  console.log(msg);
});
console.log('ファイル読み込み中でも処理が走ります。');

上記のスクリプトを実行すると、環境にもよりますが、
多くの場合、下記のような結果が表示されます。


ファイル実行中でも処理が走ります。
ほげほげふがふが

console.log('ファイル読み込み中でも処理が走ります。')
のところが、先に実行されているのがポイントです。
これは、時間のかかるファイルの読み込みの終了を待たずに
次の処理が実行されるよう非同期にコードを書いたからです。
こちらは、Node.jsの強みを生かしたよりNode.jsらしい書き方です。

ブロッキングI/OとノンブロッキングI/O

上記の結果をもう少し掘り下げると、
ノンブロッキングI/OとブロッキングI/Oというとらえ方ができます。

  • fs.readFileSync (ブロッキングI/O)
  • fs.readFile (ノンブロッキングI/O)

つまり、
ブロッキングI/Oはファイルの読み込みが完了するまで待ってから、次の処理を実行します。
ノンブロッキングI/Oは読み込みが完了するのを待たず、次の処理を実行します。

readFileSync,readFileのメリットとデメリット

両者のメリットデメリットをまとめと次のようになります。

readFileSync

  • メリット: 理解しやすく、コードもわかりやすい
  • デメリット: Node.jsのノンブロッキングI/Oの恩恵を得られない

readFile

  • メリット: ノンブロッキングI/Oによるパフォーマンスの向上
  • デメリット: コードが複雑になって、デバックしづらい

readFileをpromisify(コールバック関数地獄を軽減)

fs.readFileのサンプルコードは
コールバック関数で読み込み完了後の処理を書きました。
しかし、読み込み後の処理が複雑になると、
コールバック関数の中にコールバック関数、さらにコールバック関数と…
なりコードがとても読みにくくなってしまいます。(通称:コールバック関数地獄)
これを解決する方法として、Promiseというデザインパターンを利用する方法があります。
そして、node8から、Promiseを簡単に実装できる
promisifyという便利ツールが実装されました。
これを使ってfs.readFileのサンプルコードを書き直すと次のようになります。

const fs = require('fs');
const util = require('util');
const readFile = util.promisfy(fs.readFile);
let msg = '';
readFile('hoge.txt', 'utf-8')
  .then((data) => {
    // thenの中に完了後の処理を書く
    msg = data;
    console.log(msg);
  })
  .catch((err) => {
    // catchの中に例外処理を書く
    throw err;
  });
console.log('ファイル読み込み中でも処理が走ります。');

promise.then(正常処理)promise.catch(例外処理)のような書き方をすることで、
コールバック関数地獄が軽減されます。
とはいえ、今回のような簡単な処理の場合は、
promiseを使った処理は大げさなので、
使いどころを考えるのが重要です。

  • このエントリーをはてなブックマークに追加