3秒でわかる
他の関数へ引数として渡し、あとで呼び戻してもらう関数。処理の一部を呼ぶ側が差し込めるようにし、待ち時間のある処理の続きも書けます。
もう少し詳しく
どういうものか
コールバック関数は、別の関数に引数として渡され、渡した先から呼び出される関数です。関数そのものを値として扱えることが前提で、JavaScript や Python のように関数が第一級の値である言語で日常的に使います。
用途は大きく二つあります。並べ替えの比較規則のように処理の一部だけを外から差し替えるものと、通信やファイル読み込みのように結果が出そろってから続きを実行するものです。後者では「終わったら呼んでください」と処理の続きを預ける形になります。
なぜ必要か
同じ骨組みで違う処理をさせられるからです。配列を一件ずつ回して何かする、という骨組みは共通なので、その「何か」だけを関数として渡せば、繰り返しの記述を毎回書かずに済みます。
もう一つは、結果を待つ処理を書くためです。JavaScript は待っている間も他の処理を進めるため、戻り値として結果を受け取れません。続きを関数として預け、届いた時点で呼んでもらう形が必要になります。
具体例
const items = [
{ name: "コーヒー", price: 480 },
{ name: "紅茶", price: 420 },
{ name: "ラテ", price: 550 },
];
// 比較規則をコールバックとして渡す
items.sort((a, b) => a.price - b.price);
// 一件ずつの処理をコールバックとして渡す
items.forEach((item) => console.log(item.name, item.price));
// 完了時に呼ばれるコールバック
setTimeout(() => console.log("3秒経った"), 3000);sort に渡した関数は要素の比較のたびに呼ばれ、setTimeout に渡した関数は時間が来た一度だけ呼ばれます。呼ばれる回数も時点も、渡した先が決めます。
つまずきやすいところ
渡すときに括弧を付けてしまう間違いが最も多く見られます。setTimeout に fn() と書くと、その場で実行した戻り値を渡すことになり、目的の関数は渡っていません。括弧なしの fn を渡します。
もう一つは入れ子の深さです。通信の結果を使って次の通信をする、という連鎖をコールバックだけで書くと右へ右へ字下げが伸び、エラー処理が各段に散らばります。この形は Promise と async/await で書き直すのが定石で、現在の非同期処理はそちらが標準です。JavaScript ではコールバックの中の this が期待と違う対象を指すこともあり、アロー関数を使えばこの問題は起きません。
覚え方
「関数を渡して、あとで呼び戻してもらう」。呼ぶ側と呼ばれる側が逆転しているのがコールバックです。