概要
JavaScript は、コードを 上から順に同期的に実行する言語です。
しかし実際の開発では、setTimeout・fs.readFile・fetchなどを使うことで
**処理が「あとで実行される(非同期)」**ように見えます。
※mdn web docs – Asynchronous JavaScript
つまり、
JavaScript 自体が非同期なのではなく、
環境(Browser / Node.js)が提供する API の実装によって
非同期処理が発生しているという点が重要です。
本記事では、
- どのような処理が非同期になるのか
- なぜコールバック関数で非同期が成立するのか
を 実装視点で整理します。
目次
基本構文
同期処理の例
console.log("A");
console.log("B");
console.log("C");
出力結果
A
B
C
- 上から順に実行される
- 前の処理が終わるまで次に進まない
- JavaScript の基本的な実行モデル
非同期処理の例
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
console.log("C");
出力結果
A
C
B
-
setTimeout内の処理は「あとで」実行される - JavaScript の実行自体は止まらない
非同期を生む 3 種類の処理
JavaScript における非同期処理は、主に次の 3 種類に分類できます。
I/O(入出力系)
代表的なメソッド
| 分類 | メソッド | 実行環境 |
|---|---|---|
| ファイル | fs.readFile |
Node.js |
| ファイル | fs.writeFile |
Node.js |
| ネットワーク | fetch |
Browser / Node |
| ネットワーク | XMLHttpRequest |
Browser |
| DB | client.query |
Node.js |
なぜ後回しになるのか
- OS・ネットワーク・DB と通信する必要がある
- 完了までにかかる時間が事前に分からない
そのため、
処理完了後にコールバックを実行する設計が採用されています。
Timer(時間系)
代表的なメソッド
| 分類 | メソッド | 実行環境 |
|---|---|---|
| タイマー | setTimeout |
Browser / Node |
| タイマー | setInterval |
Browser / Node |
| 即時実行 | setImmediate |
Node.js |
| フレーム | requestAnimationFrame |
Browser |
なぜ後回しになるのか
- 「今すぐ実行」では意味がない
- 指定した時間・条件が成立するまで待つ必要がある
そのため、
処理は一度 予約(キュー登録) されます。
Event(イベント系)
代表的なメソッド
| 分類 | メソッド | 実行環境 |
|---|---|---|
| DOM | addEventListener('click', …) |
Browser |
| DOM | addEventListener('input', …) |
Browser |
| Node | process.on('exit', …) |
Node.js |
| Stream | stream.on('data', …) |
Node.js |
なぜ後回しになるのか
- ユーザー操作や外部イベントは
- いつ発生するか分からない
そのため、
発生時に呼び出す関数を登録して待つ仕組みになります。
コールバック関数と非同期の関係
結論
コールバック関数だから非同期なのではありません。
コールバック関数が「あとで呼ぶ場所」に登録されることで、
非同期が成立します。
同期コールバック
function run(fn) {
fn();
}
run(() => {
console.log("今すぐ実行");
});
console.log("次");
出力結果
今すぐ実行
次
- コールバック関数を使っている
- その場で呼ばれている
- → 同期処理
非同期コールバック
setTimeout(() => {
console.log("あとで実行");
}, 0);
console.log("先に実行");
出力結果
先に実行
あとで実行
実際に起きていること(概念)
1. JavaScript が setTimeout を呼ぶ
2. タイマーを環境に登録
3. コールバックを Task Queue に登録
4. JavaScript の実行が一段落
5. イベントループがコールバックを実行
活用例
1. I/O による非同期処理
const fs = require("fs");
fs.readFile("sample.txt", "utf-8", (err, data) => {
console.log(data);
});
console.log("読み込み中");
ポイント
-
readFileは非同期 API - 読み込み完了後にコールバックが実行される
2. Promise による非同期処理
fetch("https://example.com/data.json")
.then(res => res.json())
.then(data => {
console.log(data);
});
-
.then()もコールバック - 登録先が Microtask Queue になる点が異なる
了解です 👍
では 構成・文体・粒度はそのまま にして、
「活用例」を拡張します。
ポイントは:
- 既存の例は維持
- 「import が必要な非同期 API の例」を明示的に追加
- 「なぜ import が必要なのか」がコードから自然に読み取れる形
3. Event による非同期処理
document.addEventListener("click", () => {
console.log("クリックされました");
});
ポイント
-
addEventListenerは DOM オブジェクトのメソッド -
document自体が Browser 環境でグローバルに提供されている - クリックイベントが発生した時点でコールバックが実行される
- イベント発生タイミングは事前に分からない
4. import 構文を使った非同期 I/O の例(ES Modules)
import { readFile } from "fs/promises";
const data = await readFile("sample.txt", "utf-8");
console.log(data);
ポイント
-
fs/promisesは Node.js のモジュール - import が必須
-
readFileは Promise を返す非同期 API -
awaitにより「同期風」に記述しているが、実体は非同期
実行キューの対応関係
| 種類 | 登録先 |
|---|---|
| Timer | Task Queue |
| I/O | Task Queue |
| Event | Task Queue |
| Promise.then | Microtask Queue |
| async / await | Microtask Queue |