いちばん大事な違いのイメージ
まず、ざっくりこう覚えてください。
同期コールバック
→ 「その場で、すぐに呼ばれるコールバック」
非同期コールバック
→ 「あとで、別のタイミングで呼ばれるコールバック」
どちらも「関数を引数として渡す」のは同じですが、
「渡された関数が、いつ実行されるか」が決定的に違います。
ここが重要です。
同期コールバックは「今この呼び出しの流れの中で実行される」。
非同期コールバックは「今の処理が終わった“あと”、イベントループ経由で実行される」。
同期コールバックの具体例(今すぐその場で呼ばれる)
同期コールバックのコードと流れ
まず同期コールバックの典型例から。
function runTwice(callback) {
console.log("runTwice: start");
callback(); // 1回目(ここで即実行)
callback(); // 2回目(ここで即実行)
console.log("runTwice: end");
}
runTwice(() => {
console.log("コールバックの中");
});
JavaScript実行結果はこうなります。
runTwice: start
コールバックの中
コールバックの中
runTwice: end
流れとしては、
runTwiceが呼ばれる- 関数内で
callback()を呼んだ瞬間、コールバックが実行される - もう一度
callback()を呼ぶと、またその場で実行される - すべて終わったら
runTwiceが終わる
つまり、「runTwice の実行の一部」としてコールバックが動いています。
コールスタック的な見方
このときのコールスタックは、
- グローバル →
runTwice runTwiceの中からcallback呼び出し → グローバル → runTwice → callback- callback が終わったら、スタックから callback が消えて
runTwiceに戻る - もう一度 callback を呼ぶ → また同じように積まれてすぐ戻る
という動きです。
ポイントは、コールバックが「この関数呼び出しの流れの中で、すぐに実行されている」 こと。
これが「同期コールバック」です。
非同期コールバックの具体例(あとで呼ばれる)
setTimeout を使った非同期コールバック
今度は非同期コールバックの例です。
console.log("A: start");
setTimeout(() => {
console.log("B: timeout callback");
}, 1000);
console.log("C: end");
JavaScript実行結果はこうなります。
A: start
C: end
(1秒後)
B: timeout callback
ここで setTimeout に渡している関数が「非同期コールバック」です。
流れはこうです。
- “A: start” を出力
setTimeoutを呼ぶ
→ 「1秒後にこの関数を実行して」と Web API に渡すだけで、ここではコールバックは実行されない- “C: end” を出力
- グローバルコードが終わる(コールスタックが空になる)
- 1秒経過したタイミングで、Web API がコールバックをタスクキューに入れる
- イベントループがそのコールバックをスタックに乗せて実行し、”B: timeout callback” が出る
コールバックが実際に実行されるのは、「setTimeout を呼んだとき」ではなく、「かなりあと、別のサイクル」 です。
イベントリスナーも非同期コールバック
DOM イベントでも同じです。
const button = document.querySelector("#btn");
button.addEventListener("click", () => {
console.log("クリックされたときのコールバック");
});
console.log("リスナー登録完了");
JavaScriptaddEventListener に渡している関数も、登録時には呼ばれません。
ユーザーが「いつか」クリックしたとき、
- ブラウザがクリックを検知
- 登録されていたコールバックをタスクキューに入れる
- イベントループが、それをスタックに乗せて実行
という流れで、未来のある瞬間に「非同期コールバック」として実行されます。
並べて比較:同期コールバック VS 非同期コールバック
同じように見えるコードを書いてみる
同期コールバック:
function syncCaller(callback) {
console.log("syncCaller: before");
callback();
console.log("syncCaller: after");
}
syncCaller(() => {
console.log("同期コールバック");
});
JavaScript出力順:
syncCaller: before
同期コールバック
syncCaller: after
非同期コールバック:
function asyncCaller(callback) {
console.log("asyncCaller: before");
setTimeout(callback, 0);
console.log("asyncCaller: after");
}
asyncCaller(() => {
console.log("非同期コールバック");
});
JavaScript出力順:
asyncCaller: before
asyncCaller: after
非同期コールバック
どちらも「関数を引数に渡している」のは同じなのに、
「いつ実行されるか」が根本的に違う のがわかると思います。
コールスタックとキューの違い
同期コールバックは、
- 呼び出し元関数の「中で」すぐにコールスタックに積まれ、その場で処理される
非同期コールバックは、
- いったん Web API に登録され
- 条件が揃ったときにタスクキューやマイクロタスクキューに入り
- 「今実行中のタスク(スタック)の処理が終わってから」イベントループによってスタックに積まれる
という「一呼吸置いた」動きをします。
ここが重要です。
同期コールバックは「現在のスタックの一部」、非同期コールバックは「次かそのまた次のタスクとして実行される別枠の処理」 と考えると、頭が整理しやすくなります。
もう少しだけ深く:コードの読み方がどう変わるか
同期コールバックは「順番どおりに読めばいい」
同期コールバックは、基本的にこう読めます。
上から順に処理が進む
コールバック呼び出しの行に来たら、その関数の中身が即実行される
終われば元の関数に戻る
さっきの syncCaller の例では、
- “before”
- コールバック(”同期コールバック”)
- “after”
と、コードの見た目どおりの順番で動きます。
「その場で関数を呼び出しているだけ」と考えれば OK です。
非同期コールバックは「ここから先は“別タイミングの処理”」と意識する
非同期コールバックが出てくる行に来たら、頭の中でこう切り替えます。
「あ、ここから先(コールバックの中身)は、“今すぐ”ではなく“あとで”の処理なんだな」
例えば:
console.log("1");
setTimeout(() => {
console.log("2: 非同期コールバック");
}, 0);
console.log("3");
JavaScript読み方としては、
- “1” を出す
- 「2 の処理」を“あとで実行するために登録する”(今は実行しない)
- “3” を出す
- いまのタスクが全部終わって、キューから非同期コールバックが拾われたタイミングで “2” が出る
という二段階に分けて考える必要があります。
ここが重要です。
非同期コールバックの行に来たら、「ここから下の処理は、今この連続の世界から一旦切り離される」と意識すること。
これができると、非同期コードの見通しが一気によくなります。
どっちをいつ使うのか
同期コールバックが向いている場面
同期コールバックが似合うのは、
- 配列の各要素に対して「すぐに」何かしたいとき(
forEach,mapなど) - 自前の関数ライブラリで「処理の一部」を外から差し込みたいとき
- 時間のかからない処理を順番通りに組み合わせたいとき
例えば:
[1, 2, 3].forEach((n) => {
console.log(n * 2);
});
JavaScriptこれは完全に「その場で」実行されるので、同期コールバックです。
非同期コールバックが必須になる場面
非同期コールバックは、次のようなときに必須です。
- 一定時間待ってから処理したい(
setTimeout,setInterval) - ユーザー操作に応じて処理したい(クリック、キー入力などのイベント)
- サーバー通信やファイル読み込みなど、いつ終わるか分からない処理の結果を扱いたい
これらは、「終わるまで待ち続ける」と UI が固まってしまうので、
「結果が出たときに呼ばれる非同期コールバック」という形にせざるを得ません。
まとめ:同期コールバックと非同期コールバックの違いを一文で
最後にギュッとまとめます。
同期コールバックは、
「呼び出し元の関数の中で、その場ですぐ実行されるコールバック」。
実行タイミングは「今ここ」です。
非同期コールバックは、
「タイマー、イベント、通信などが完了した“あと”、イベントループを通じて別タイミングで実行されるコールバック」。
実行タイミングは「今ここではなく、未来のどこか」です。
これからコードを読むとき、コールバックを見かけたら、まず自分にこう問いかけてみてください。
「これは同期か? 非同期か?」
「この関数は“今すぐ”実行されるのか? それとも“あとで”実行されるのか?」
この 2 つを意識できるようになると、
非同期処理のコードは、ぐっと怖くなくなります。
