ファイルを読み込む
前のセクションではコマンドライン引数からファイルパスを取得して利用できるようになりました。 このセクションでは渡されたファイルパスを元にMarkdownファイルを読み込んで、標準出力に表示してみましょう。
fsモジュールを使ってファイルを読み込む
前のセクションで取得できるようになったファイルパスを元に、ファイルを読み込みましょう。
Node.jsでファイルの読み書きを行うには、標準モジュールのfsモジュールを使います。
まずは読み込む対象のファイルを作成しましょう。sample.mdという名前でmain.jsと同じnodecliディレクトリに配置します。
sample.md
# sample
fsモジュール
fsモジュールは、Node.jsでファイルの読み書きを行うための基本的な関数を提供するモジュールです。
fsモジュールは同期形式と非同期形式の両方が提供されています。
同期APIと非同期APIはどちらもfsモジュールに含まれていますが、
非同期形式のAPIはfs/promisesというモジュール名でも参照できるようになっています。
この書籍では分かりやすさのために、非同期形式のみのAPIを提供するfs/promisesモジュールを利用します。
fs/promisesの非同期APIは、モジュール名からもわかるようにPromiseを返します。
ファイルの読み書きといった非同期処理が成功したときには、返されたPromiseインスタンスがresolveされます。
一方、ファイルの読み書きといった非同期処理が失敗したときには、返されたPromiseインスタンスがrejectされます。
次のサンプルコードは、指定したファイルを読み込むfs/promisesのreadFileメソッドの例です。
// 非同期APIを提供するfs/promisesモジュールを読み込む
const fs = require("fs/promises");
fs.readFile("sample.md").then(file => {
console.log(file);
}).catch(err => {
console.error(err);
});
そして、次のサンプルコードは、同じく指定したファイルを読み込むfsモジュールのreadFileSyncメソッドの例です。
Node.jsでは非同期APIと同期APIがどちらもあるAPIには、分かりやすくSyncがメソッド名の末尾に含まれています。
// 同期APIを提供するfsモジュールを読み込む
const fs = require("fs");
try {
const file = fs.readFileSync("sample.md");
} catch (err) {
// ファイルが読み込めないなどのエラーが発生したときに呼ばれる
}
Node.jsはシングルスレッドなので、他の処理をブロックしにくい非同期形式のAPIを選ぶことがほとんどです。
Node.jsにはfs/promisesモジュール以外にも多くの非同期APIがあるので、非同期処理に慣れておきましょう。
readFile関数を使う
それではfs/promisesモジュールのreadFileメソッドを使ってsample.mdファイルを読み込んでみましょう。
次のようにmain.jsを変更し、コマンドライン引数から取得したファイルパスを元にファイルを読み込んでコンソールに出力します。
main.js
const program = require("commander");
// fs/promisesモジュールをfsオブジェクトとしてインポートする
const fs = require("fs/promises");
// コマンドライン引数からファイルパスを取得する
program.parse(process.argv);
const filePath = program.args[0];
// ファイルを非同期で読み込む
fs.readFile(filePath).then(file => {
console.log(file);
});
sample.mdを引数に渡した実行結果は次のようになります。
文字列になっていないのは、コールバック関数の第二引数はファイルの中身を表すBufferインスタンスだからです。
Bufferインスタンスはファイルの中身をバイト列として保持しています。
そのため、そのままconsole.logメソッドに渡しても人間が読める文字列にはなりません。
$ node main.js sample.md
<Buffer 23 20 73 61 6d 70 6c 65>
fs.readFile関数は引数によってファイルの読み込み方を指定できます。
ファイルのエンコードを第二引数であらかじめ指定しておけば、自動的に文字列に変換された状態でコールバック関数に渡されます。
次のようにmain.jsを変更し、読み込まれるファイルをUTF-8として変換させます。
main.js
const program = require("commander");
const fs = require("fs/promises");
program.parse(process.argv);
const filePath = program.args[0];
// ファイルをUTF-8として非同期で読み込む
fs.readFile(filePath, { encoding: "utf8" }).then(file => {
console.log(file);
});
先ほどと同じコマンドをもう一度実行すると、実行結果は次のようになります。
sample.mdファイルの中身を文字列として出力できました。
$ node main.js sample.md
# sample
エラーハンドリング
先ほどの例では触れませんでしたが、fsモジュールのコールバック関数の第一引数には常にエラーオブジェクトが渡されます。
ファイルの読み書きは存在の有無や権限、ファイルシステムの違いなどによって例外が発生しやすいので、必ずエラーハンドリング処理を書きましょう。
次のようにmain.jsを変更し、errオブジェクトがnullまたはundefinedではないことだけをチェックするシンプルなエラーハンドリングです。
エラーが発生していたときにはエラーメッセージを表示し、process.exit関数に終了ステータスを指定してプロセスを終了しています。
ここでは、一般的なエラーを表す終了ステータスの1でプロセスを終了しています。
main.js
const program = require("commander");
const fs = require("fs/promises");
program.parse(process.argv);
const filePath = program.args[0];
// ファイルを非同期で読み込む
fs.readFile(filePath, { encoding: "utf8" }).then(file => {
console.log(file);
}).catch(err => {
console.error(err.message);
// 終了ステータス 1(一般的なエラー)としてプロセスを終了する
process.exit(1);
});
存在しないファイルであるnotfound.mdをコマンドライン引数に渡して実行すると、次のようにエラーが発生して終了します。
$ node main.js notfound.md
ENOENT: no such file or directory, open 'notfound.md'
これでコマンドライン引数に指定したファイルを読み込んで標準出力に表示できました。 次のセクションでは読み込んだMarkdownファイルをHTMLに変換する処理を追加していきます。
[コラム] Node.jsのエラーファーストコールバック
Node.jsが提供するfsモジュールは同期APIと非同期APIを提供するという話を紹介しました。
歴史的な経緯もあり、Node.jsではPromiseとエラーファーストコールバックの2種類の非同期APIを提供しているケースもあります。
fs/promisesモジュールでは、readFileメソッドは、Promiseを返す非同期APIでした。
一方で、fsモジュールにもreadFileメソッドがあり、このAPIはエラーファーストコールバックを扱う非同期APIです。
// fsモジュールにはエラーファーストコールバックを扱う非同期APIも含まれる
const fs = require("fs");
// エラーファーストコールバックの第1引数にはエラー、第2引数 には結果が入るというルール
fs.readFile("sample.md", (err, file) => {
if (err) {
console.error(err.message);
process.exit(1);
return;
}
console.log(file);
});
エラーファーストコールバックについては、非同期の章でも紹介しています。
エラーファーストコールバックは、PromisesがECMAScriptに入るES2015より前においては、非同期な処理を扱う方法として広く使われていました。
Node.jsの多くのモジュールは、ES2015より前に作られているため、fsモジュールのようにエラーファーストコールバックを扱うAPIもあります。
一方で、Promiseが非同期APIの主流となったため、Node.jsにもPromiseを扱うためのAPIが追加されました。
しかし、すでにエラーファーストコールバックを提供する同じ名前のメソッドがあったため、fsに対してfs/promisesのようにモジュールとして分けて扱えるようになっています。
また、Node.jsではエラーファーストコールバックを受け取る非同期APIをPromiseを返す非同期APIへとラップするutil.promisifyというメソッドも提供しています。
Node.jsでは、歴史的な経緯からエラーファーストコールバックとPromiseのAPIがどちらも提供されていることがあります。 しかしながら、両方が提供されている場合はPromiseのAPIを利用するべきです。 Promiseを扱うAPIには、他のPromiseを扱う処理との連携のしやすさ、Async Functionという構文的なサポート、エラーハンドリングの簡潔さなどのメリットがあります。
このセクションのチェックリスト
fsモジュールのreadFile関数を使ってファイルを読み込んだ- UTF-8形式のファイルの中身をコンソールに出力した
readFile関数の呼び出しにエラーハンドリング処理を記述した