JavaScript/TypeScriptメモ

【Node.js】ファイルの入出力処理を行う(同期処理、Promiseによる非同期処理)

Node.jsでファイル操作を行う

Node.jsでファイルの入出力処理を行うには、次の方法があります。

  1. 同期処理 (←おすすめ)
  2. コールバックによる非同期処理
  3. Promiseによる非同期処理 (←おすすめ)

同期処理で良い状況なら、同期処理を使うのがシンプルです。

もし、非同期処理を書く必要があるときには、Promiseによる非同期処理を書くほうが見やすく書けるので、おすすめです。

それぞれの方法でファイル入出力処理を書く

「ファイルを読み込んで、その内容を別のファイルに出力する」プログラムをTypeScriptで書いてみます。

下記環境で、プログラムを実行しています。

  • Node.js 12.13.0
  • TypeScript 4.0.3
  • ts-node 9.0.0

1. 同期処理

import fs from 'fs';

const inputFilepath = './file/input.txt';
const outputFilepath = './file/output_syncronous.txt';

const main = () => {
  try {
    const data = fs.readFileSync(inputFilepath, 'utf-8');
    console.log(data);

    fs.writeFileSync(outputFilepath, data);
    console.log(`Write to ${outputFilepath}`);
  } catch (e) {
    //エラー処理
    console.log(e);
  }
};

main();

fsモジュールをimportし、fs.readFileSyncでファイル読み込み、fs.writeFileSyncでファイル出力を行います。

すべて、同期的に処理されるので、流れが、とてもわかりやすいです。


なお、ファイル操作に失敗した場合には例外が発生します。そこで、trycatchを使って、例外処理を行いましょう。

2. コールバックによる非同期処理

import fs from 'fs';

const inputFilepath = './file/input.txt';
const outputFilepath = './file/output_callback.txt';

const main = () => {
  fs.readFile(inputFilepath, 'utf-8', (err, data) => {
    if (err) {
      //エラー処理
      console.log(err);
      return;
    }
    console.log(data);
    fs.writeFile(outputFilepath, data, (err) => {
      if (err) {
        //エラー処理
        console.log(err);
        return;
      }
      console.log(`Write to ${outputFilepath}`);
    });
  });
};

main();

先ほどと同じようにfsモジュールをimportします。

コールバックによる非同期処理をする場合には、fs.readFileでファイル読み込み、fs.writeFileでファイル出力を行います。


これらの関数を使う場合には、3つめの引数に、コールバック関数を渡す必要があります。

たとえば、読み込み処理の場合には、(err, data) => { ... }の形のコールバック関数を渡します。処理がうまくいった場合には、引数dataに、処理結果が格納されるので、この引数を使って後続処理を書いていくことになります。

逆に、うまくいかなかった場合にはerrに値が設定されるので、その内容を使ってエラー処理を行ってください。


同様に、出力処理の場合には、(err) => { ... }の形のコールバック関数を渡して、後続処理を記述していきます。


コールバック関数を使うと、ネストがどんどん深くなるので、とても記述がしにくいです。ですから、この方法を使うことはおすすめしません

3. Promiseによる非同期処理

import { promises as fs } from 'fs';
const inputFilepath = './file/input.txt';
const outputFilepath = './file/output_promise.txt';

const main = async () => {
  try {
    const data = await fs.readFile(inputFilepath, 'utf-8');
    console.log(data);

    await fs.writeFile(outputFilepath, data);
    console.log(`Write to ${outputFilepath}`);
  } catch (e) {
    //エラー処理
    console.log(e);
  }
};

main();

Promiseによる非同期処理をする場合には、まずimport文の書き方を変える必要があります。import { promises as fs } from 'fs';というようにしてインポートをしてください。

そして、fs.readFileでファイル読み込み、fs.writeFileでファイル出力を行います。


Promiseを使う書き方には何パターンか書き方がありますが、上の例では、async-awaitを使って記述をしています。

まず、ファイル入出力をする関数mainasync関数として宣言します。

そして、fs.readFilefs.writeFileの処理時にawaitキーワードを付けて処理を行います。

こうすることで、同期処理と、ほとんど同じ書き方で書くことができます。


もし、asyncawaitを使わずに、thenを使って書くのであれば、次のような感じに書くことができます。

import { promises as fs } from 'fs';

const inputFilepath = './file/input.txt';
const outputFilepath = './file/output_promise2.txt';

const main = () => {
  fs.readFile(inputFilepath, 'utf-8')
    .then((data) => {
      console.log(data);
      return fs.writeFile(outputFilepath, data);
    })
    .then(() => {
      console.log(`Write to ${outputFilepath}`);
    })
    .catch((e) => {
      //err処理
      console.log(e);
    });
};

main();

括弧は増えましたが、それでも、コールバックを使う書き方よりは、見やすいでしょう。

githubリポジトリ

今回のプログラムは、下記に上げています。

https://github.com/m-haketa/node-fs