同期処理は、その処理が完了して結果が返るまで次の文へ進みません。非同期処理は時間のかかる操作を開始し、完了結果をcallbackやPromiseとして後から受け取ります。network、timer、user inputの待ちには非同期APIを使い、結果に依存する部分だけをawaitします。ただしasync functionへ変えるだけで重い計算が軽くなったり、処理が自動でparallelになったりはしません。
同期と非同期は何が違う?
違いは「結果をいつ受け取るか」と「待つ間に呼び出し元が進めるか」です。同期functionは通常のreturn値を返します。Promise-basedな非同期functionは、将来の成功値または失敗理由を表すPromiseをすぐ返します。
| 項目 | 同期 | 非同期 |
|---|---|---|
| 結果 | 呼び出し中にreturnする | Promiseやcallbackで後から受け取る |
| 向く処理 | 短い計算、即時の値変換 | network、timer、user操作、非同期storage |
| error | throwをtry/catchする | Promise rejectionをawaitとtry/catch等で扱う |
| 注意 | 長い処理はmain threadを塞ぐ | 順序、取消、重複実行を設計する |
AJAXは歴史的に、page全体をreloadせずserverと非同期通信して表示を更新するWeb開発手法を指します。現在はXMLHttpRequestだけでなくFetch APIとJSONを使う構成が一般的です。「AJAX」という別の実行モデルがあるのではなく、HTTP通信を非同期に扱う利用例です。XMLHttpRequestとPromiseの書き方の違いはJavaScriptのXMLHttpRequestとPromiseの違いで比較しています。
「Web API」という言葉は、この文脈ではbrowserが提供する機能(fetch、setTimeout、DOM eventなど)を指します。JavaScriptの言語仕様そのものには時間を待つ仕組みがなく、待ち時間の処理はこれらのbrowser側のAPIが引き受け、完了後にcallbackやPromiseの結果としてJavaScriptへ戻します。
Promiseは何を表す?
Promiseは非同期操作の最終的な完了または失敗を表すobjectです。pendingからfulfilledまたはrejectedへsettleし、一度settleした状態は変わりません。Promiseの中身を同期的に取り出すことはできず、then、catch、finallyまたはawaitで後続処理をつなぎます。
function wait(milliseconds) {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
wait(500)
.then(() => {
console.log("完了");
})
.catch((error) => {
console.error(error);
});
Promise constructorで包むのはcallback APIをPromise化するときに必要です。すでにPromiseを返すfetchなどを新しいPromiseで二重に包む必要はありません。setTimeoutとsetIntervalそのものの挙動はsetIntervalとsetTimeoutの使い方で扱っています。
async/awaitはどう使う?
async functionは必ずPromiseを返します。awaitは周囲のasync functionの続きだけをPromiseのsettleまで保留し、fulfilled値を取り出すか、rejectionをthrowとして扱います。
async function loadUser(userId, signal) {
const response = await fetch("/api/users/" + userId, { signal });
if (!response.ok) {
throw new Error("ユーザー取得に失敗しました: " + response.status);
}
return response.json();
}
fetchはnetwork errorやabortではrejectしますが、404や500などHTTP error statusだけでは通常rejectしません。そのためresponse.okまたはstatusを確認して、自分で失敗として扱います。JSONの構造も外部入力なので、必要なfieldと型を別に検証してください。
逐次実行と並行開始はどう選ぶ?
前の結果が次の入力になるなら逐次実行します。独立した複数操作なら先にPromiseを作り、まとめて待つと総待ち時間を減らせます。ここでいう並行開始は、JavaScriptのCPU計算が同じthreadでparallelになるという意味ではありません。
// 逐次: userIdが分かってからpostsを取得する
const user = await fetchUser();
const posts = await fetchPosts(user.id);
// 独立: profileとsettingsを先に開始する
const [profile, settings] = await Promise.all([
fetchProfile(),
fetchSettings(),
]);
| 組み合わせ | 完了条件 | 向く場面 |
|---|---|---|
| 順番にawait | 各処理を1つずつ待つ | 依存関係がある、負荷を順にかけたい |
Promise.all() |
全成功。1つrejectすると結果もreject | 全結果が必要な独立処理 |
Promise.allSettled() |
全Promiseがsettle | 一部失敗も含めて全結果を確認する |
Promise.race() |
最初にsettleしたもの | 明確なrace semanticsが必要な場合 |
Promise.any() |
最初のfulfilled | 複数候補のどれか1つ成功すればよい場合 |
同時に大量requestを開始するとserverやbrowserへ負荷をかけます。件数が多い場合はconcurrency上限を設け、retryと重複送信の影響を確認します。
非同期処理をキャンセルするには?
Promise一般に共通のcancel methodはありません。Fetch APIなど対応するAPIではAbortControllerのsignalを渡します。画面遷移、検索語の更新、component破棄など、結果が不要になった時点でabortします。
const controller = new AbortController();
try {
const user = await loadUser("42", controller.signal);
console.log(user);
} catch (error) {
if (error.name === "AbortError") {
console.log("取得を中止しました");
} else {
throw error;
}
}
// 必要がなくなった時点で呼ぶ
controller.abort();
abortしたこととserver側の処理が必ず取り消されたことは同義ではありません。既にserverへ届いた更新requestは処理される場合があります。重要な更新ではidempotencyや重複防止をserver側でも設計します。
よくある失敗はどう直す?
| 症状 | 原因候補 | 戻し方・対策 |
|---|---|---|
| Promiseが画面に表示される | 結果をawaitせずそのまま使った | async contextでawaitし、loading/error状態も用意する |
| errorがcatchされない | Promiseをreturn・awaitしていない | chainを返し、最上位でrejectionを処理する |
| 404なのに成功処理へ進む | fetchのresponse.ok未確認 | bodyを読む前にstatusを判定する |
| 古い検索結果で上書きされる | 複数requestの完了順が入れ替わる | 前requestをabortするかrequest IDで最新だけ反映する |
| UIが固まる | await前後に重い同期計算がある | algorithmを改善し、必要ならworkerへ分離する |
順序問題を一定時間のsetTimeoutで隠すと、通信速度が変わったとき再発します。完了条件をPromiseとして表すか、stateを識別するIDを持たせます。event loopのtaskとmicrotaskまで追う必要がある場合はJavaScriptのevent loopを確認してください。
実装手順をどう組み立てる?
- 待ちが必要な境界を特定し、Promiseを返すAPIか確認する
- 成功値、HTTP error、network error、abortを分ける
- 処理間の依存関係を図にし、逐次か独立か決める
- loading中の二重実行と、結果不要時のcancelを設計する
- 成功・失敗・遅延・順序逆転のtestを行う
まず1つのfetchをtry/catchで閉じ、response.ok、JSON検証、画面のloading/errorを確認します。その後で独立requestをPromise.allへまとめます。APIごとの実装はFetch APIでJSONを取得する方法、async/awaitの文法とerror伝播はasync/awaitの解説へ進んでください。非同期化は速さを保証する機能ではなく、待ちと依存関係を明示する設計です。
関連する内容は、JavaScript async/awaitの使い方|エラー処理と並行実行、JavaScriptのイベントループ入門|task・microtask・実行順を理解するもあわせて確認してください。