JavaScript | 非同期処理:コールバック – コールバック関数とは

JavaScript JavaScript
スポンサーリンク

コールバック関数のイメージ(まず感覚から)

コールバック関数は、
「あとで呼んでね」と他の関数に渡しておく関数 です。

自分で直接 myFunc() と呼ぶのではなく、

「この処理が終わったときに、この関数を実行して」
「ボタンが押されたら、この関数を実行して」

という“約束”として関数を渡しておく。
その「渡される側に登録される関数」が、コールバック関数です。

ここが重要です。
コールバックは 「今すぐ実行する関数」ではなく、「条件が満たされたときに呼び返してもらう関数」 という役割を持っています。


コールバック関数の一番シンプルな形

普通の関数呼び出しとの違い

まず普通の関数呼び出しはこうです。

function sayHello() {
  console.log("こんにちは");
}

sayHello(); // ここで即実行される
JavaScript

これは「自分で直接呼んでいる」状態です。

一方、コールバックはこうなります。

function sayHello() {
  console.log("こんにちは");
}

function runCallback(callback) {
  console.log("コールバックを実行します");
  callback(); // ここで「渡された関数」を実行
}

runCallback(sayHello);
JavaScript

runCallback に「引数として関数を渡している」のがポイントです。
runCallback の中で callback() が呼ばれたタイミングで、初めて sayHello が実行されます。

このとき sayHello は、runCallback にとっての「コールバック関数」です。

無名関数をその場で渡すことも多い

コールバックは、その場で匿名の関数を渡すこともよくあります。

function runCallback(callback) {
  console.log("コールバック開始");
  callback();
  console.log("コールバック終了");
}

runCallback(function () {
  console.log("これは無名のコールバックです");
});
JavaScript

あるいはアロー関数で:

runCallback(() => {
  console.log("アロー関数のコールバックです");
});
JavaScript

「関数を引数として渡している」ことがコールバックの基本形です。


非同期処理とコールバックの関係(ここが本丸)

「終わったら呼んで」が非同期処理と相性が良い

非同期の世界では、

  • いつ終わるか分からない(サーバー通信・タイマー・ユーザー操作)
  • 終わったタイミングで何かしたい

という状況がとても多いです。

この「終わったタイミングで何かしたい、けど終わるまで待ち続けたくはない」というときに、
コールバック関数が活躍します。

setTimeout とコールバック

とても有名な例が setTimeout です。

console.log("A: start");

setTimeout(() => {
  console.log("B: 1秒後のコールバック");
}, 1000);

console.log("C: end");
JavaScript

ここで setTimeout の第1引数に渡している () => { ... } がコールバック関数です。

流れを整理すると、

  1. “A: start” を出力
  2. setTimeout に「1秒後にこの関数を実行して」とコールバックを渡す
  3. “C: end” を出力(ここまでが同期)
  4. 1秒後、ブラウザが「渡されていたコールバック関数」を呼び出し、”B: 1秒後のコールバック” が出力される

ここが重要です。
コールバックは「あとで実行される処理」を渡すための仕組み。
非同期処理において、「結果が出たらこのコールバックを実行して」と書けることが鍵になります。

DOM イベントとコールバック

ユーザー操作のイベントも、コールバックの典型例です。

const button = document.querySelector("#myButton");

button.addEventListener("click", () => {
  console.log("ボタンがクリックされました");
});
JavaScript

addEventListener の第2引数に渡している () => { ... } がコールバック関数です。

あなたは「クリックされたらこれを実行して」と登録するだけ。
実際にいつクリックされるかはユーザー次第で、
クリックされた瞬間にブラウザがそのコールバックを呼び出します。


コールバックをもう少し抽象的に捉えてみる

「処理の一部を、他人に預ける」という考え方

例えば、こういう関数を考えてみます。

function doTwice(callback) {
  callback();
  callback();
}

doTwice(() => {
  console.log("Hello");
});
// Hello
// Hello
JavaScript

doTwice は、「2回呼びたい処理」を自分では知りません。
「何をするか」は、外から渡されるコールバック関数に任せています。

これが「処理の一部を他人に渡す」イメージです。

非同期でも同じで、

  • 「いつ終わるか」の管理は Web API や他の関数に任せる
  • 「終わったときに何をするか」だけ、コールバックとして渡しておく

という役割分担ができます。

引数や結果をコールバックに渡すこともできる

コールバック関数には、普通の関数と同じように引数を渡せます。

function greet(name, callback) {
  console.log("こんにちは、" + name + "さん");
  callback(name);
}

greet("太郎", (name) => {
  console.log(name + "さんの処理が終わりました");
});
JavaScript

非同期でも同様です。

setTimeout(() => {
  const result = 42;
  console.log("計算結果:", result);
}, 1000);
JavaScript

「いつコールバックが呼ばれるか」は呼ぶ側が決め、
「呼ばれたときに何をするか」はコールバック側が決める。
この分業がコールバックの本質です。


コールバック地獄(callback hell)とその背景

ネストしまくると読みづらくなる問題

コールバックは便利ですが、非同期処理を何段もつなげると一気に読みづらくなります。

例えば:

doSomething((result1) => {
  doSomethingElse(result1, (result2) => {
    doThird(result2, (result3) => {
      console.log("全部終わった:", result3);
    });
  });
});
JavaScript

どのカッコがどこに対応しているか分かりづらくなり、
エラー処理や分岐が入るとすぐカオスになります。
これがいわゆる「コールバック地獄」です。

この問題を解決するために生まれたのが Promiseasync/await です。
これらは「コールバックを完全に捨てた」のではなく、
「中身ではコールバックを使いつつ、表面上はもっと読みやすくする仕組み」 だと思ってください。

それでも「コールバック的な発想」は残り続ける

Promise や async/await を使うと、書き方は変わりますが、

  • 結局「あとで結果が返ってきたときに実行される処理」をどこかに登録している
  • 「いつ実行するか」自体は、イベントループやランタイム側が管理している

という構造は変わりません。

なので、

「ある処理が終わったタイミングで呼ばれる関数」
という意味での「コールバック的な考え方」は、非同期処理を理解するうえで根っこの部分に残り続けます。


まとめ:コールバック関数とは何者か

コールバック関数を一言で言い直すと、

「あるタイミングで(すぐではなく)呼び返してもらうために、他の関数や API に渡しておく関数」
です。

押さえておきたいポイントは次の通りです。

コールバックは「関数を引数として渡す」ことで成立する
setTimeout の第1引数、addEventListener の第2引数などが典型的なコールバック
非同期処理では、「終わったらこのコールバックを実行して」と書けることが重要
コールバックをネストしすぎると「コールバック地獄」になるため、Promise / async/await が導入された
それでも「あるタイミングで呼ばれる処理を登録しておく」というコールバック的発想は非同期の根っこにある

最初の一歩としては、次のような小さな練習がおすすめです。

同期的な関数を自作して、引数にコールバックを受け取る
setTimeout でログを出すコードを書いて、「ログの順番」を予想してから実行する
ボタンのクリックイベントで "クリックされた" を出すコードを「これはコールバックだ」と意識しながら書く

コールバックが「ただの難しい単語」ではなく、
「あとで呼んでもらうために渡しておく関数なんだ」と体感できたら、
この先の Promise や async/await の理解が一気に楽になります。

タイトルとURLをコピーしました