コールバック関数のイメージ(まず感覚から)
コールバック関数は、
「あとで呼んでね」と他の関数に渡しておく関数 です。
自分で直接 myFunc() と呼ぶのではなく、
「この処理が終わったときに、この関数を実行して」
「ボタンが押されたら、この関数を実行して」
という“約束”として関数を渡しておく。
その「渡される側に登録される関数」が、コールバック関数です。
ここが重要です。
コールバックは 「今すぐ実行する関数」ではなく、「条件が満たされたときに呼び返してもらう関数」 という役割を持っています。
コールバック関数の一番シンプルな形
普通の関数呼び出しとの違い
まず普通の関数呼び出しはこうです。
function sayHello() {
console.log("こんにちは");
}
sayHello(); // ここで即実行される
JavaScriptこれは「自分で直接呼んでいる」状態です。
一方、コールバックはこうなります。
function sayHello() {
console.log("こんにちは");
}
function runCallback(callback) {
console.log("コールバックを実行します");
callback(); // ここで「渡された関数」を実行
}
runCallback(sayHello);
JavaScriptrunCallback に「引数として関数を渡している」のがポイントです。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引数に渡している () => { ... } がコールバック関数です。
流れを整理すると、
- “A: start” を出力
setTimeoutに「1秒後にこの関数を実行して」とコールバックを渡す- “C: end” を出力(ここまでが同期)
- 1秒後、ブラウザが「渡されていたコールバック関数」を呼び出し、”B: 1秒後のコールバック” が出力される
ここが重要です。
コールバックは「あとで実行される処理」を渡すための仕組み。
非同期処理において、「結果が出たらこのコールバックを実行して」と書けることが鍵になります。
DOM イベントとコールバック
ユーザー操作のイベントも、コールバックの典型例です。
const button = document.querySelector("#myButton");
button.addEventListener("click", () => {
console.log("ボタンがクリックされました");
});
JavaScriptaddEventListener の第2引数に渡している () => { ... } がコールバック関数です。
あなたは「クリックされたらこれを実行して」と登録するだけ。
実際にいつクリックされるかはユーザー次第で、
クリックされた瞬間にブラウザがそのコールバックを呼び出します。
コールバックをもう少し抽象的に捉えてみる
「処理の一部を、他人に預ける」という考え方
例えば、こういう関数を考えてみます。
function doTwice(callback) {
callback();
callback();
}
doTwice(() => {
console.log("Hello");
});
// Hello
// Hello
JavaScriptdoTwice は、「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どのカッコがどこに対応しているか分かりづらくなり、
エラー処理や分岐が入るとすぐカオスになります。
これがいわゆる「コールバック地獄」です。
この問題を解決するために生まれたのが Promise や async/await です。
これらは「コールバックを完全に捨てた」のではなく、
「中身ではコールバックを使いつつ、表面上はもっと読みやすくする仕組み」 だと思ってください。
それでも「コールバック的な発想」は残り続ける
Promise や async/await を使うと、書き方は変わりますが、
- 結局「あとで結果が返ってきたときに実行される処理」をどこかに登録している
- 「いつ実行するか」自体は、イベントループやランタイム側が管理している
という構造は変わりません。
なので、
「ある処理が終わったタイミングで呼ばれる関数」
という意味での「コールバック的な考え方」は、非同期処理を理解するうえで根っこの部分に残り続けます。
まとめ:コールバック関数とは何者か
コールバック関数を一言で言い直すと、
「あるタイミングで(すぐではなく)呼び返してもらうために、他の関数や API に渡しておく関数」
です。
押さえておきたいポイントは次の通りです。
コールバックは「関数を引数として渡す」ことで成立するsetTimeout の第1引数、addEventListener の第2引数などが典型的なコールバック
非同期処理では、「終わったらこのコールバックを実行して」と書けることが重要
コールバックをネストしすぎると「コールバック地獄」になるため、Promise / async/await が導入された
それでも「あるタイミングで呼ばれる処理を登録しておく」というコールバック的発想は非同期の根っこにある
最初の一歩としては、次のような小さな練習がおすすめです。
同期的な関数を自作して、引数にコールバックを受け取るsetTimeout でログを出すコードを書いて、「ログの順番」を予想してから実行する
ボタンのクリックイベントで "クリックされた" を出すコードを「これはコールバックだ」と意識しながら書く
コールバックが「ただの難しい単語」ではなく、
「あとで呼んでもらうために渡しておく関数なんだ」と体感できたら、
この先の Promise や async/await の理解が一気に楽になります。

