PR

【JavaScript】コールバック関数とは?意味・使い方・Promiseとの違いをやさしく解説

JavaScript

JavaScriptの学習で「コールバック関数」という言葉が出てくると、一気に難しく感じやすいです。結論からいうと、コールバック関数とは「別の関数に引数として渡して、あとで実行してもらう関数」です。

意味だけ聞くと抽象的ですが、実際は配列処理やイベント処理、非同期処理で毎日のように使われます。この記事では、コールバック関数の意味、基本例、非同期処理との関係、Promiseとの違いを初心者向けに整理します。

「あとで呼び出すために渡す関数」と言い換えると、かなり分かりやすくなります。

スポンサーリンク

結論:コールバック関数は処理の差し込み口

まずはシンプルな例を見てみます。

function processUser(name, callback) {
  console.log("処理開始");
  callback(name);
}

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

processUser("田中", greet);

この例では、greet がコールバック関数です。processUser に渡されて、内部であとから実行されています。

つまり、コールバック関数は「処理の流れのどこかで実行したい追加処理」を外から差し込むための仕組みです。

1. なぜコールバック関数を使うのか

コールバック関数を使うと、親の関数の中身を固定しすぎず、実行する処理だけを外から差し替えられます。

たとえば、同じ「データ取得後の処理」でも、ある画面では表示したい、別の画面では保存したい、ということがあります。そんなとき、結果の後処理だけをコールバックとして渡せば、関数の再利用性が高まります。

2. 配列処理でもコールバックは使われている

コールバック関数は非同期処理だけの話ではありません。配列の forEachmap でも使われています。

const numbers = [1, 2, 3];

numbers.forEach(function(number) {
  console.log(number);
});

forEach に渡している関数がコールバック関数です。配列の要素1つ1つに対して、あとで順番に呼び出されています。

この形はアロー関数と相性がよく、実務では次のようにもよく書きます。

numbers.forEach(number => {
  console.log(number);
});

forEach の記事アロー関数の記事もあわせて読むと、実際の見え方がつかみやすいです。

3. 非同期処理でのコールバック関数

コールバック関数が特に重要になるのは、時間がかかる処理の完了後に何かをしたい場面です。

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

ここでは、setTimeout に渡した関数が2秒後に実行されます。これも立派なコールバック関数です。

つまり、「今すぐではなく、ある条件がそろった後で呼び出す関数」がコールバックだと考えると整理しやすいです。

4. Promise や async/await と何が違うのか

昔からJavaScriptの非同期処理ではコールバック関数がよく使われてきました。ただ、非同期処理が何段も続くと、関数の入れ子が深くなって読みにくくなることがあります。

step1(function() {
  step2(function() {
    step3(function() {
      console.log("完了");
    });
  });
});

このような状態は「コールバック地獄」と呼ばれます。そこで、今のJavaScriptでは Promise や async/await を使って、もっと読みやすく非同期処理を書くことが増えました。

ただし、だからといってコールバック関数が不要になるわけではありません。イベント処理、配列メソッド、ライブラリのAPIなどでは今でも頻繁に登場します。

5. よくあるつまずき

関数を渡すのではなく、その場で実行してしまう

コールバック関数では、greet を渡すのであって、greet() を先に実行するわけではありません。この違いは非常に大切です。

processUser("田中", greet);   // OK
// processUser("田中", greet()); // これは別物

引数の流れが分からなくなる

親の関数からコールバックへ、どの値が渡されるのかを追う必要があります。慣れるまでは、サンプルコードを紙に書いて流れを追うと理解しやすいです。

非同期処理だけの用語だと思ってしまう

実際には forEachmap のような同期処理でも使われています。コールバックは「使われる場面」ではなく、「関数の渡し方」の名前だと考えるのがポイントです。

補足:コールバック関数を実務で読むための見方

コールバック関数は、言葉だけ見ると難しそうですが、実際には「あとで実行してほしい処理を、先に渡しておく仕組み」です。イベント処理、配列処理、API通信など、JavaScriptではかなり頻繁に登場します。

まずは、コールバックを処理の流れとして見てみます。ポイントは、関数をその場で実行するのではなく、別の関数に預けて、必要なタイミングで呼び出してもらうことです。

JavaScriptのコールバック関数が後で呼ばれる流れを説明した図
コールバック関数は、関数を渡し、親の関数が必要なタイミングであとから実行する仕組みです。

この図で見てほしいのは、「渡す」と「呼ぶ」が別のタイミングで起きることです。コールバック関数は、渡した瞬間に必ず実行されるわけではありません。親の関数の中で、条件がそろったとき、クリックされたとき、通信が終わったときなどに実行されます。

実務でよく見るのは、ボタンのクリック処理です。画面のボタンに対して「クリックされたらこの関数を実行してね」と登録する処理は、コールバックの代表例です。

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

button.addEventListener("click", sayHello);

この例では、sayHello をその場で実行しているのではなく、クリックされたときに実行してもらうために渡しています。ここで重要なのが、関数名の後ろに () を付けないことです。

JavaScriptで関数を渡す場合と実行する場合の違いを示した図
関数を渡すときはカッコなし、関数をその場で実行するときはカッコありです。

この画像では、sayHellosayHello() の違いを示しています。sayHello は関数そのものを渡しているため、クリック後に実行されます。一方で sayHello() はその場で関数を実行してしまい、イベントに登録したい処理としては意図とずれます。

初心者の方が混乱しやすいのは、「関数を渡す」と「関数を実行する」が見た目では少ししか違わないことです。カッコがあるかないかだけですが、意味は大きく変わります。コールバックを読むときは、まずその関数が今実行されるのか、あとで実行されるのかを確認しましょう。

もうひとつ大切なのは、コールバック関数は非同期処理だけの用語ではないという点です。forEachmap のような配列処理でも、引数として渡している関数はコールバックです。

const numbers = [1, 2, 3];

numbers.forEach(number => {
  console.log(number);
});

この例では、number => { ... } がコールバック関数です。配列の要素ごとにあとから呼ばれる処理として渡されています。配列処理でコールバックに慣れておくと、Promiseや async/await の理解にもつながります。

関連して、関数そのものの基本は function(関数)、短く書く記法は アロー関数、非同期処理の整理は async/await を読むと理解がつながりやすいです。

コールバックを読むときのチェックリスト

コールバック関数を読むときは、いきなり中身を細かく追うより、まず「誰が、いつ、この関数を呼ぶのか」を確認すると整理しやすいです。配列処理なら配列の各要素ごと、イベント処理ならクリックされたとき、通信処理なら結果が返ってきたとき、というように実行タイミングを先に押さえます。

  • 関数を渡しているだけなのか、その場で実行しているのか
  • コールバックに渡される引数は何か
  • 戻り値を使う処理なのか、表示や保存などの副作用を起こす処理なのか
  • 非同期処理なら、どの順番で実行されるのか

特に非同期処理では、コードに書いた順番と実行される順番が一致しないことがあります。たとえば、タイマー、API通信、ファイル読み込みなどは、後から処理が完了します。その「完了したあとに何をするか」を渡すのが、コールバックの重要な役割です。

console.log("開始");

setTimeout(() => {
  console.log("あとで実行");
}, 1000);

console.log("終了");

このコードでは、出力順は「開始」「終了」「あとで実行」になります。setTimeout に渡した関数は、すぐには実行されず、指定した時間のあとに呼び出されるからです。

コールバックで混乱しやすい実務場面

実務でコールバックが分かりにくくなるのは、処理が何段階もつながったときです。たとえば、ボタンを押す、入力値をチェックする、APIへ送る、結果を受け取る、画面を書き換える、という流れでは、それぞれのタイミングで関数が呼ばれます。

このとき、どの関数が最初に実行され、どの関数があとから実行されるのかを整理しないと、コードが急に読みにくくなります。特に、通信処理では結果が返ってくるまで時間がかかるため、「送った直後」と「結果が返った後」を分けて考えることが大切です。

最近のJavaScriptでは、非同期処理を Promiseasync/await で書くことも多いですが、内部では「処理が終わったら次に何をするか」という考え方が残っています。つまり、コールバックを理解しておくと、より新しい書き方も読みやすくなります。

このように、コールバック関数は「処理の順番を後ろへ回す」「処理の一部を外から差し込む」ための基本的な考え方です。最初は難しく感じますが、イベント処理や配列処理の身近な例から慣れていくと、Promiseやasync/awaitも理解しやすくなります。

注意点として、コールバックに渡す関数をその場で実行してしまうミスはとても多いです。イベントに登録したいのに () を付けてしまうと、クリック前に処理が走ってしまい、クリック時には何も起きないように見えることがあります。

もうひとつのよくあるミスは、コールバックの中で使う値が、実行されるタイミングでは変わっているケースです。特にループや非同期処理では、コードを書いた順番だけでなく、実際にいつ呼ばれるかを意識する必要があります。

コールバックを読むときは、「渡している関数」「受け取っている関数」「実行されるタイミング」を分けてメモすると整理しやすいです。最初は少し面倒に感じますが、この3つを分けるだけで、イベント処理やAPI通信のコードがかなり読みやすくなります。

実務では、ボタンを押した後の処理、入力フォームの変更検知、通信完了後の画面更新など、ユーザー操作とコールバックが強く結びつきます。つまり、コールバックは文法だけでなく、画面の動きそのものを理解するための考え方でもあります。

慣れてきたら、コールバックで書かれた処理を async/await ではどう表すかを比べてみると理解が深まります。書き方は変わっても、「ある処理が終わった後に次の処理を実行する」という基本は同じです。

最初は、ボタンや配列処理のような身近な例から確認すると十分です。小さな例で「あとで呼ばれる」感覚をつかめると、非同期通信のコードも段階的に読みやすくなります。

迷ったときは、関数名の後ろにカッコがあるかを最初に確認しましょう。

関連して学びたい記事

まとめ

まとめ コールバック関数の基本

  • コールバック関数は、別の関数に渡してあとで実行してもらう関数
  • 配列処理、イベント処理、非同期処理でよく使われる
  • 関数そのものを渡すのであって、先に実行するわけではない
  • 非同期処理が深く入れ子になると読みにくくなる
  • Promise や async/await は、その読みにくさを改善するための考え方

コールバック関数は、最初は抽象的に見えますが、「後で呼び出すための関数を渡しているだけ」と分かると一気に読みやすくなります。JavaScriptの非同期処理を理解する土台にもなるので、ここでしっかり押さえておくのがおすすめです。

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