同期処理と非同期処理の違いを理解しよう
学習の目標
本章では、以下の内容を学習します。
- 同期処理の特徴と動作を理解する
- 非同期処理が必要な理由を学ぶ
- JavaScriptのシングルスレッドとは何かを理解する
- イベントループの基本概念を学ぶ
はじめに
これまでのJavaScriptの学習では、コードが上から下へ順番に実行されることを当然のこととして扱ってきました。しかし、実際のWebアプリケーションでは、このような単純な処理だけでは対応できない場面が数多くあります。
例えば、サーバーからデータを取得する処理や、ユーザーのボタンクリックを待つ処理などは、完了するまでに時間がかかる場合があります。もしこれらの処理が完了するまで他の処理が一切動かなくなってしまったら、ユーザーにとって非常に使いにくいアプリケーションになってしまうでしょう。
そこで重要になるのが、同期処理と非同期処理の概念です。この違いを理解することで、より実用的なWebアプリケーションを作ることができるようになります。
同期処理とは何か
同期処理とは、コードが書かれた順番通りに実行され、一つの処理が完了するまで次の処理に進まない実行方式のことです。これまで皆さんが書いてきたJavaScriptのコードは、ほとんどが同期処理でした。
まずは、VS Codeでsynchronous.html
というファイルを作成し、同期処理の動作を確認してみましょう。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>同期処理の例</title>
</head>
<body>
<h1>同期処理の例</h1>
<script>
console.log("処理1: 開始");
// 重い処理をシミュレート
function heavyTask() {
const start = Date.now();
while (Date.now() - start < 3000) {
// 3秒間ループし続ける
}
return "重い処理が完了しました";
}
console.log("処理2: 重い処理を開始");
const result = heavyTask();
console.log("処理3: " + result);
console.log("処理4: 終了");
</script>
</body>
</html>
このファイルをブラウザで開き、開発者ツールのConsoleタブを確認してみてください。コンソールには以下のような順序でメッセージが表示されるはずです。
処理1: 開始
処理2: 重い処理を開始
処理3: 重い処理が完了しました
処理4: 終了
この例では、heavyTask()
という関数が3秒間実行され続けますが、その間は他の処理が一切実行されません。これが同期処理の特徴です。
同期処理の問題点
同期処理では、時間のかかる処理が実行されている間、ブラウザが完全に固まってしまいます。実際に上記のコードを実行すると、3秒間ブラウザが応答しなくなることが確認できるでしょう。
この問題を体感するために、次のコードを追加してみましょう。同じファイルの<body>
タグ内に、スクリプトの前に以下を追加してください。
<body>
<h1>同期処理の例</h1>
<!-- ここから追加 -->
<button onclick="alert('ボタンがクリックされました')">クリックしてみて</button>
<p>重い処理中はこのボタンも押せなくなります</p>
<!-- 追加ここまで -->
<script>
// 既存のコード...
</script>
</body>
このページを開いて、重い処理が実行されている間にボタンをクリックしてみてください。ボタンは反応せず、処理が完了するまで待たされることがわかります。
これが同期処理の大きな問題点です。一つの処理に時間がかかると、ユーザーの操作を含む他のすべての処理が停止してしまいます。
非同期処理とは何か
非同期処理とは、時間のかかる処理を開始した後、その処理の完了を待たずに次の処理を実行できる仕組みのことです。時間のかかる処理は「バックグラウンドで実行」され、完了したタイミングで結果が通知されます。
新しくasynchronous.html
というファイルを作成し、非同期処理の動作を確認してみましょう。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>非同期処理の例</title>
</head>
<body>
<h1>非同期処理の例</h1>
<button onclick="alert('ボタンがクリックされました')">クリックしてみて</button>
<p>非同期処理中でもこのボタンは押せます</p>
<script>
console.log("処理1: 開始");
// 非同期処理(3秒後に実行される)
setTimeout(function() {
console.log("処理3: 非同期処理が完了しました");
}, 3000);
console.log("処理2: 非同期処理を開始しました(すぐに次へ進む)");
console.log("処理4: 終了");
</script>
</body>
</html>
このファイルをブラウザで開き、コンソールを確認してみてください。メッセージは以下のような順序で表示されます。
処理1: 開始
処理2: 非同期処理を開始しました(すぐに次へ進む)
処理4: 終了
処理3: 非同期処理が完了しました(3秒後)
注目してほしいのは、「処理3」が最後に表示されることです。これは、setTimeout
で指定した処理が非同期で実行されているためです。
(setTimeout については次のレッスンで詳しく解説するので、いまはなんとなくの理解で大丈夫です。)
さらに重要なのは、この3秒の間もブラウザが固まらず、ボタンをクリックできることです。実際にボタンを押してみると、待機時間中でも正常に反応することが確認できます。
JavaScriptのシングルスレッドとは
ここで疑問に思うかもしれません。「JavaScriptは非同期処理ができるなら、同時に複数の処理を実行しているのでしょうか?」
実は、JavaScriptはシングルスレッドで動作します。つまり、同時に実行できる処理は常に一つだけです。それなのになぜ非同期処理が可能なのでしょうか。
答えは、JavaScriptエンジンとブラウザの協力にあります。時間のかかる処理(タイマーやネットワーク通信など)は、JavaScriptエンジンではなくブラウザ側で管理されます。そして、処理が完了したタイミングで、結果がJavaScriptエンジンに戻されます。
これを具体的に見てみましょう。新しくsingle-thread.html
というファイルを作成してください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>シングルスレッドの確認</title>
</head>
<body>
<h1>シングルスレッドの確認</h1>
<script>
console.log("1. 最初の処理");
// 0秒後に実行される非同期処理
setTimeout(function() {
console.log("3. タイマー処理(0秒後)");
}, 0);
console.log("2. 同期処理");
// 重い同期処理
function heavyTask() {
const start = Date.now();
while (Date.now() - start < 2000) {
// 2秒間ループ
}
}
heavyTask();
console.log("4. 重い処理が完了");
</script>
</body>
</html>
このコードを実行すると、コンソールには以下の順序でメッセージが表示されます。
1. 最初の処理
2. 同期処理
4. 重い処理が完了
3. タイマー処理(0秒後)
setTimeout
の待機時間を0秒に設定したにも関わらず、「タイマー処理」は最後に実行されています。これは、JavaScriptが一度に一つの処理しか実行できないためです。重い同期処理が完了するまで、非同期処理の結果も待機させられています。
イベントループの基本概念
この仕組みを理解するためには、イベントループという概念を知る必要があります。イベントループは、JavaScriptの非同期処理を支える重要な仕組みです。
イベントループは、簡単に説明すると以下のような流れで動作します。
まず、JavaScriptエンジンはコールスタックという場所で処理を実行します。同期処理はすべてここで順番に実行されます。
次に、非同期処理(setTimeout
やイベント処理など)が呼び出されると、その処理はブラウザに依頼され、JavaScriptエンジンはすぐに次の処理に進みます。
そして、ブラウザで非同期処理が完了すると、その結果はコールバックキューという待機場所に置かれます。
最後に、コールスタックが空になったタイミングで、イベントループがコールバックキューから処理を取り出し、コールスタックに送ります。
この仕組みを理解するために、次のコードで動作を確認してみましょう。event-loop.html
というファイルを作成してください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>イベントループの動作</title>
</head>
<body>
<h1>イベントループの動作</h1>
<script>
console.log("A: 同期処理1");
setTimeout(function() {
console.log("D: 1秒後のタイマー");
}, 1000);
setTimeout(function() {
console.log("C: 0秒後のタイマー");
}, 0);
console.log("B: 同期処理2");
</script>
</body>
</html>
このコードを実行すると、以下の順序でメッセージが表示されます。
A: 同期処理1
B: 同期処理2
C: 0秒後のタイマー
D: 1秒後のタイマー
この順序になるのは、以下の理由によります。
最初に、「A: 同期処理1」がコールスタックで実行されます。
次に、1秒後のタイマーがブラウザに依頼され、JavaScriptエンジンはすぐに次の処理に進みます。
続いて、0秒後のタイマーもブラウザに依頼され、これもすぐに次の処理に進みます。
そして、「B: 同期処理2」がコールスタックで実行されます。
この時点でコールスタックが空になり、イベントループが動作し始めます。0秒後のタイマーは既に完了しているので、「C: 0秒後のタイマー」が実行されます。
最後に、1秒後にタイマーが完了し、「D: 1秒後のタイマー」が実行されます。
まとめ
本章では、同期処理と非同期処理の基本概念について学習しました。今回学んだ内容は以下の通りです。
- 同期処理は順番通りに実行されるが、時間のかかる処理があると他の処理が停止してしまう
- 非同期処理を使うことで、時間のかかる処理中でも他の処理を継続できる
- JavaScriptはシングルスレッドで動作するが、ブラウザとの協力により非同期処理を実現している
- イベントループという仕組みにより、非同期処理の結果が適切なタイミングで実行される
これらの概念は、実用的なWebアプリケーションを作る上で必要不可欠です。次回からは、実際に非同期処理を使ったプログラムの書き方を学んでいきましょう。
Starterプランでより詳しく学習
この先のコンテンツを読むにはStarterプラン以上が必要です。より詳細な解説、実践的なサンプルコード、演習問題にアクセスして学習を深めましょう。