コールバック関数の基本と仕組み

JavaScript では、関数を別の関数に引数として渡すことができる。この「引数として渡される関数」をコールバック関数と呼ぶ。非同期処理の土台となる重要な概念であり、イベント処理やタイマー、データ取得など幅広い場面で使われている。

コールバック関数とは何か

コールバック関数の本質は「あとで呼んでもらう関数」だ。自分で直接呼び出すのではなく、ある処理が完了したタイミングで自動的に実行される仕組みになっている。

function greet(name) {
  console.log(`Hello, ${name}!`);
}

function processUser(callback) {
  const user = "Alice";
  callback(user);
}

processUser(greet); // Hello, Alice!

この例では greet 関数を processUser に渡している。processUser の内部で callback(user) が呼ばれた時点で、はじめて greet が実行される。関数を値として扱えるという JavaScript の特徴が、コールバックの仕組みを支えている。

同期コールバックと非同期コールバック

コールバック関数には大きく分けて 2 つの種類がある。

同期コールバック

渡した関数がその場ですぐに実行される。配列の forEachmap などが代表例で、処理は上から順に進む。

非同期コールバック

渡した関数がすぐには実行されず、特定の条件が満たされたときに実行される。setTimeout やイベントリスナーが代表例だ。

同期コールバックは直感的でわかりやすい。配列メソッドで日常的に使っているものがまさにそれだ。

const numbers = [1, 2, 3, 4, 5];

// map に渡す関数は同期コールバック
const doubled = numbers.map(function(num) {
  return num * 2;
});

console.log(doubled); // [2, 4, 6, 8, 10]

一方、非同期コールバックは少し異なる動きをする。

console.log("開始");

setTimeout(function() {
  console.log("2秒後に実行");
}, 2000);

console.log("終了");

// 出力順序:
// 開始
// 終了
// 2秒後に実行

setTimeout に渡したコールバックは 2 秒後に実行されるため、先に「終了」が出力される。この非同期的な振る舞いが JavaScript の大きな特徴であり、コールバックが非同期処理の基盤となる理由でもある。

コールバックの実践的な使い方

実際の開発ではイベント処理でコールバックを多用する。ボタンのクリックやキーボード入力に反応する処理は、すべてコールバック関数として登録される。

document.getElementById("btn").addEventListener("click", function() {
  console.log("ボタンがクリックされた");
});

addEventListener の第 2 引数がコールバック関数だ。ユーザーがボタンをクリックするまでこの関数は実行されず、クリックというイベントが発生したタイミングで呼び出される。

もうひとつよく見るパターンが、処理の成功と失敗を別々のコールバックで受け取る形式だ。

function fetchData(onSuccess, onError) {
  const data = { id: 1, name: "Alice" };
  const success = true;

  if (success) {
    onSuccess(data);
  } else {
    onError("データの取得に失敗しました");
  }
}

fetchData(
  function(data) { console.log("成功:", data); },
  function(err) { console.log("失敗:", err); }
);

関数を引数として渡す

特定のタイミングで渡した関数が実行される

結果に応じた処理が走る

このように、コールバック関数は JavaScript における非同期処理の最も基本的なパターンだ。ただし、コールバックが多重にネストすると可読性が大きく低下する問題がある。この問題は「コールバック地獄」と呼ばれ、Promise や async/await が登場する動機となった。