非同期処理とPromiseを理解しよう
更新日:2026-08-02
前の記事では、ブラウザがサーバーへHTTPリクエストを送り、サーバーが処理結果をHTTPレスポンスとして返す流れを学びました。
HTTP通信では、リクエストを送ってからレスポンスが届くまでに時間がかかります。JavaScriptでは、サーバーからの応答を待つ処理にPromiseとasync・awaitがよく使われます。
JavaScriptは応答を待っている間も、クリックやスクロールなど別の処理を進められます。そしてレスポンスが届いた後に、取得したデータを画面へ表示します。このように、完了を待つ間に別の処理を進める仕組みが非同期処理です。
非同期処理は最初につまずきやすい分野です。まず実行順序を観察し、その後にPromiseが成功値と失敗理由をどのように渡すかを段階的に確認します。
今回使用するファイル構成
javascript-practice/
├── index.html
└── js/
└── script.js
HTTP通信と非同期処理の関係
サーバーの応答には時間がかかる
ブラウザとサーバーはネットワークを通して通信します。サーバー側でも、データベースからの取得、入力内容の検証、外部サービスとの通信などが行われる場合があります。
そのため、リクエストを送った直後にレスポンスを利用できるとは限りません。
ブラウザがHTTPリクエストを送る
↓
サーバーがリクエストを受け取る
↓
サーバーが必要な処理を行う
↓
サーバーがHTTPレスポンスを返す
↓
ブラウザがレスポンスを受け取って画面を更新する
通信環境、サーバーの混雑、処理するデータ量などによって、待ち時間は変わります。通信に失敗し、レスポンスを正常に受け取れないこともあります。
待っている間も画面の操作を続ける
サーバーの応答が届くまでJavaScript全体を停止すると、利用者はメニューを開く、スクロールする、別のボタンを押すといった操作ができなくなります。
非同期処理では、通信の完了を待つ処理をいったん預け、ほかのJavaScriptを先へ進めます。通信が完了したら、その結果を使う処理を再開します。
記事データの取得を開始する
├── サーバーの応答を待つ
└── ブラウザはほかの操作を受け付ける
↓
レスポンスが届く
↓
取得した記事を画面へ表示する
Promiseが通信結果を表す
次の記事で学ぶfetch()は、HTTP通信の結果をPromiseとして返します。
const responsePromise = fetch('/api/articles');
console.log(responsePromise);
この時点のresponsePromiseには、記事データそのものが入っているわけではありません。「通信が完了したら結果を受け取れる」という将来の結果を表しています。
Promiseを使うと、次の二つを分けて処理できます。
- サーバーから正常にレスポンスを受け取ったときの処理
- 通信に失敗してレスポンスを受け取れなかったときの処理
この仕組みを理解してから、次の記事で実際にサーバーや外部APIへHTTPリクエストを送ります。
非同期処理の結果を待つ流れ
同期処理との違い
console.log('1. 開始');
setTimeout(() => {
console.log('2. タイマー完了');
}, 1000);
console.log('3. 次の処理');
Consoleには開始、次の処理、タイマー完了の順に表示されます。タイマーの完了を待たず、次の行が実行されるためです。
Promiseの三つの状態
Promiseは、非同期処理の将来の結果を表します。
pending:処理中fulfilled:成功rejected:失敗
const wait = (milliseconds) => new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
wait(1000).then(() => {
console.log('1秒待ちました');
});
asyncとawaitで結果を待つ
async function run() {
console.log('開始');
await wait(1000);
console.log('完了');
}
run();
awaitはPromiseの完了を待ちます。awaitを使う関数にはasyncを付けます。画面全体を停止するのではなく、その関数内の続きが待機します。
Promiseから値を受け取る
resolveで成功値を渡す
function getMessage(delay) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`${delay}ミリ秒後のメッセージ`);
}, delay);
});
}
getMessage(1000).then((message) => {
console.log(message);
});
resolve()へ渡した値が、.then()のコールバック引数へ届きます。Promiseは「後で値を渡す約束」を表すオブジェクトです。
rejectで失敗理由を渡す
function getUser(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId <= 0) {
reject(new Error('ユーザーIDが不正です'));
return;
}
resolve({ id: userId, name: '山田太郎' });
}, 500);
});
}
getUser(0)
.then((user) => {
console.log(user.name);
})
.catch((error) => {
console.error(error.message);
});
失敗には文字列ではなくErrorオブジェクトを使うと、メッセージと発生場所を調査できます。成功後は.then()、失敗時は.catch()へ進みます。
asyncとawaitで読みやすく書く
同じgetUser()をasync関数から利用します。
async function showUser(userId) {
try {
console.log('取得を開始します');
const user = await getUser(userId);
console.log(user.name);
} catch (error) {
console.error(error.message);
} finally {
console.log('処理が終了しました');
}
}
showUser(1);
awaitの右側にはPromiseを置きます。成功すると結果が戻り値のように変数へ入り、失敗すると例外が発生してcatchへ移ります。
複数処理を順番に待つ
async function run() {
const first = await getMessage(1000);
console.log(first);
const second = await getMessage(500);
console.log(second);
}
この場合は合計約1.5秒かかります。前の結果を次で使わない処理なら、同時に開始できます。
async function runInParallel() {
const [first, second] = await Promise.all([
getMessage(1000),
getMessage(500),
]);
console.log(first, second);
}
Promise.all()はすべての成功を待ちます。一つでも失敗すると全体が失敗します。
実行順序を予想する練習
コードを実行する前に、Consoleへ表示される順序を書き出してください。
console.log('A');
getMessage(0).then(() => {
console.log('B');
});
console.log('C');
答えはA、C、Bです。待ち時間が0でも、Promise完了後のコールバックは現在の同期処理が終わった後に実行されます。
よくあるエラー
await is only valid...:async関数の外でawaitしていないか確認する- Promiseの結果が
undefined:resolve()または関数のreturnを確認する - エラーが処理されない:
.catch()またはtryとcatchを用意する - 処理が遅い:独立した処理を不必要に一件ずつ
awaitしていないか確認する
まとめ
非同期処理は完了を待つ間も他の処理を進めます。Promiseをasyncとawaitで扱う流れを理解しましょう。次の記事ではfetch()でAPIへ通信します。