コールバック関数

コールバック関数は関数の引数などで関数を渡す手法です。 コールバック関数は非同期処理を取り扱う上で最も簡単な手法になります。 非同期での処理が完了するとコールバック関数に処理の結果を引数として与えて処理が終了したことを通知します。

サンプルコード

非同期処理をコールバック関数を利用して記述したコードは以下の様になります。

import { readFile } from 'node:fs';

// ファイルを読み出す処理の指示を出す
readFile('/path/to/file', (err, data) => {
  // ファイルの読み込みが完了するとこのコールバック関数が実行されます

  if (err) {
    // エラーが発生している場合には err にエラーに関する情報が入っています
    console.error('Error !!', err);
    return;
  }

  // 正常な場合には取得したデータが data の引数として与えられます
  console.log('Data !!', data);
});

// readFile の呼び出しが終わればファイルの読み込みが完了していなくても、次に記述した処理が実行される
console.log('after readFile');

コールバック関数を利用して終了を通知する関数は一般的に処理の呼び出しをした段階で関数自体を呼び出し元に戻します。 そのため、上のコードでは readFile の次に記述されている console.log の呼び出しが直ぐに呼び出されます。 一般的には readFile の様な非同期処理を前提とした関数では処理の開始指示を出した段階で直ぐに関数を呼び出し元に戻すことが多いです。 処理の途中や最後まで同期的に待たせてから処理を戻すといったことは通常しません。

問題点

コールバック関数を利用した非同期処理には、『処理が複雑になると大量のコールバック関数が入れ子になってしまいコードが複雑になる』といった問題があります。 実際のプログラムで利用される非同期処理は以下の様に手順が複雑であることが多いためコールバック関数を利用利用して記述するとコードが複雑になり過ぎることが多いです。

  1. パスを指定してファイルを読み出す
  2. ファイルの内容を解析して新しいデータを作成する
  3. 新しく作成されたデータを別のファイルに保存する

この処理を JavaScript で記述すると以下のコードの様になります。

import { readFile, writeFile } from 'node:fs';

readFile('/path/to/file', (err, data) => {
  if (err) {
    console.error('Error !!', err);
    return;
  }

  const newData = doSomething(data)
  writeFile('/path/to/out-file', newData, (err) => {
    if (err) {
      console.error('Error !!', err);
      return;
    }

    console.log('success');
  });
});

この手続きに更にエラーや例外処理を加えて手順を複雑にしていくと人間に管理するのが難しいコードになっていくと思います。

参考資料