Promise: 非同期処理の基礎

コールバック地獄を解消する非同期処理の標準パターン。async/awaitの基盤。

Promise
非同期処理の結果を表すオブジェクト。
pending
処理中の状態。
fulfilled
処理が成功した状態。
rejected
処理が失敗した状態。

Promiseとは?

注文番号

Promise は「レストランの注文番号」。料理(結果)がまだでも番号札があれば待てる。できたら呼ばれる(resolve)、ダメなら断られる(reject)。

なぜPromiseが必要か?

Promiseは「非同期処理の結果を表すオブジェクト」です。JavaScriptでは、ネットワークリクエスト、ファイル読み込み、タイマーなど、時間がかかる処理を非同期で行う必要があります。コールバック地獄を解消するために導入されました。Promiseを使うと、非同期処理を同期的に書けるため、コードが読みやすく、エラーハンドリングも簡単になります。

いつ使うか?

Promiseは、以下のような場面で使います: 1. **ネットワークリクエスト**: APIからデータを取得するとき。 2. **ファイル操作**: ファイルの読み込みや書き込み。 3. **タイマー**: 指定時間後に処理を実行したいとき。 4. **並列実行**: 複数の非同期処理を同時に実行したいとき。 5. **チェーン処理**: 複数の非同期処理を順番に実行したいとき。

実践テクニック

Promiseチェーンを避ける

`.then().then().then()` のようなPromiseチェーンは、コードが読みにくくなります。代わりに `async/await` を使うと、同期的なコードのように書けます。また、`Promise.all()` を使うと、並列実行が簡潔に書けます。

エラーハンドリング

Promiseには `.catch()` メソッドがあり、エラーハンドリングが可能です。また、`async/await` を使うと、`try/catch` ブロックでエラーをキャッチできます。エラーが発生した場合、適切なエラーメッセージを表示し、ユーザーに通知することが重要です。

Promise.all() vs Promise.race()

`Promise.all()` は「すべてのPromiseが完了するまで待つ」ため、並列実行に使われます。`Promise.race()` は「最初に完了したPromiseが完了するまで待つ」ため、タイムアウトや最速応答を取得する場面に使われます。例:複数のAPIを同時にリクエストして、最速でレスポンスを取得したい場合、`Promise.race()` を使います。

Promise.allSettled()

`Promise.allSettled()` は「すべてのPromiseが完了するか失敗するまで待つ」ため、一部が失敗しても他の処理を続けたい場面に使われます。例:複数のAPIリクエストのうち、一部が失敗しても、成功したレスポンスを処理したい場合に便利です。

Promise.finally()

`Promise.finally()` は「Promiseが完了または失敗した後に実行する処理」を定義できます。例:ローディングインジケータを非表示にする場合に便利です。また、`try/catch/finally` ブロックと組み合わせて使うこともできます。

async/awaitとPromiseの使い分け

`async/await` は「同期的なコードのように書ける」ため、通常は `async/await` を使います。ただし、`Promise.all()`、`Promise.race()`、`Promise.allSettled()` などのPromiseメソッドは、並列実行やエラーハンドリングに便利です。また、既存のコールバック地獄関数をPromiseでラップする場合、`Promise.resolve()` や `Promise.reject()` を使います。

Promiseのデバッグ

Promiseは非同期処理であるため、デバッグが難しい場合があります。Chrome DevToolsの「Sources」タブでブレークポイントを設定し、Promiseの状態を確認してください。また、`console.log()` を使って、Promiseの状態(pending、fulfilled、rejected)を出力することも有効です。

Basic Promise
// Promise の作成
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// 使用
wait(1000).then(() => console.log('1秒経過!'));
Bad
// ❌ Bad: コールバック地獄
getUser(id, (user) => {
getPosts(user, (posts) => {
getComments(posts[0], (comments) => {
console.log(comments);
});
});
});
Good
// ✅ Good: Promise チェーン
getUser(id)
.then(user => getPosts(user))
.then(posts => getComments(posts[0]))
.then(comments => console.log(comments))
.catch(error => console.error(error));

async/await

ES2017以降は async/await で同期的に書ける。内部はPromise。

async/await
// さらに良い: async/await
async function fetchData() {
try {
const user = await getUser(id);
const posts = await getPosts(user);
const comments = await getComments(posts[0]);
console.log(comments);
} catch (error) {
console.error(error);
}
}
Tip: async 関数は自動的に Promise を返す。

並列実行

Promise.all / race / allSettled
// 並列実行
const [users, products] = await Promise.all([
fetch('/api/users'),
fetch('/api/products')
]);
// 最初に完了したもの
const fastest = await Promise.race([
fetch('/api/fast'),
fetch('/api/slow')
]);
// 全て完了まで待つ(成功/失敗問わず)
const results = await Promise.allSettled([
fetch('/api/1'),
fetch('/api/2'),
fetch('/api/3') // 失敗しても他を待つ
]);

合格ライン

Promise の3つの状態を言える
.then().catch() を書ける
async/await に書き換えられる

演習課題

課題1: Promise チェーン
.then().catch() でエラーハンドリングしてください。
課題2: async/await
Promise を async/await に書き換えてください。

参考文献

この記事は以下の公的ガイドライン/標準に基づいています。