Promise: 非同期処理の基礎
コールバック地獄を解消する非同期処理の標準パターン。async/awaitの基盤。
Promiseとは?
なぜ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)を出力することも有効です。
// Promise の作成const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// 使用wait(1000).then(() => console.log('1秒経過!'));// ❌ Bad: コールバック地獄getUser(id, (user) => { getPosts(user, (posts) => { getComments(posts[0], (comments) => { console.log(comments); }); });});// ✅ 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/awaitasync 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); }}並列実行
// 並列実行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') // 失敗しても他を待つ]);合格ライン
演習課題
参考文献
この記事は以下の公的ガイドライン/標準に基づいています。
- MDN - Promise - 公式ドキュメント
- MDN - Using Promises - Promiseの使い方
- MDN - Promise.all() - 並列実行メソッド
- MDN - Promise.race() - 最速応答メソッド
- MDN - Promise.allSettled() - 全ての完了を待つメソッド