更新: 2026年03月 · 12 分で読める · 5,923 文字
Promiseとasync/awaitの使い分け:実装パターンで理解する違い
JavaScriptの非同期処理に欠かせないPromiseとasync/awaitの違いを理解することで、読みやすく保守しやすいコードを書くことができます。本記事では、両者の特性を比較し、実践的な使い分けルールを解説します。
JavaScriptの非同期処理の歴史と位置づけ
JavaScriptで非同期処理が必要になる場面は、API呼び出しやファイル読み込み、データベースアクセスなど多岐にわたります。これらの処理結果を待つ必要がありますが、ブロッキングすると画面が固まるため、非同期で処理する仕組みが生まれました。
最初はコールバック関数で対応していましたが、ネストが深くなる「コールバック地獄」という問題が発生。これを解決するためにPromiseが登場し、さらに読みやすい記法としてasync/awaitが導入されました。async/awaitはPromiseの上に構築された糖衣構文であり、根本的には同じ仕組みを使っています。
Promiseの基本:チェーン可能な非同期処理
Promiseの基本構文と3つの状態
Promiseは「pending(待機中)」「fulfilled(成功)」「rejected(失敗)」の3つの状態を持ちます。一度状態が決まると、二度と変わることはありません。
// Promiseの基本形
const myPromise = new Promise((resolve, reject) => {
// 時間がかかる処理
setTimeout(() => {
const success = true;
if (success) {
resolve('処理が成功しました'); // fulfilled状態へ
} else {
reject('エラーが発生しました'); // rejected状態へ
}
}, 1000);
});
// Promiseを使う側
myPromise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('処理終了'));
Promiseチェーンで複数の非同期処理を繋ぐ
実務では複数のAPIを順序立てて呼び出す必要があります。Promiseチェーンはこのような場面で活躍します。
// ユーザー情報を取得 → ユーザーの投稿を取得 → コメントを取得
fetch('/api/user/123')
.then(response => response.json())
.then(user => {
console.log('ユーザー:', user);
return fetch(`/api/posts?userId=${user.id}`);
})
.then(response => response.json())
.then(posts => {
console.log('投稿:', posts);
return fetch(`/api/comments?postId=${posts[0].id}`);
})
.then(response => response.json())
.then(comments => console.log('コメント:', comments))
.catch(error => console.error('エラー:', error));
Promise.all()で複数の処理を並列実行
処理の順序が不要な場合、Promise.all()で並列実行するとパフォーマンスが向上します。
// 3つのAPI呼び出しを並列実行
Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
])
.then(([users, posts, comments]) => {
console.log('全データ取得完了', { users, posts, comments });
})
.catch(error => console.error('いずれかのAPIが失敗:', error));
async/awaitの実力:同期処理のような書き方
async/awaitの基本と利点
async/awaitを使うと、非同期処理を同期処理のような形で書けます。コードの読みやすさが大幅に向上し、ロジックの追跡が容易になります。
// async/awaitで記述
async function getUserWithPosts(userId) {
try {
// awaitで各処理の完了を待つ
const userResponse = await fetch(`/api/user/${userId}`);
const user = await userResponse.json();
console.log('ユーザー:', user);
const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsResponse.json();
console.log('投稿:', posts);
return { user, posts };
} catch (error) {
console.error('エラー:', error);
}
}
// 関数の呼び出し
getUserWithPosts(123).then(result => console.log(result));
async関数は常にPromiseを返す
重要なポイント:async関数は必ずPromiseを返します。async/awaitを使っていても、内部的にはPromiseで動作しており、呼び出し側ではPromiseとして扱う必要があります。
// async関数は自動的にPromiseでラップされる
async function fetchData() {
return 'データ'; // Promiseでラップされる
}
// 呼び出し側
const result = fetchData(); // これはPromise
console.log(result instanceof Promise); // true
result.then(value => console.log(value)); // 'データ'
並列実行でasync/awaitの威力が発揮される
複数の非同期処理を並列実行する場合、async/awaitの記述方法がPromiseチェーンより直感的です。
// 【良い例】Promise.all()を使った並列実行
async function getMultipleData() {
try {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
console.log({ users, posts, comments });
} catch (error) {
console.error('エラー:', error);
}
}
// 【悪い例】順序実行になってしまう
async function getMultipleDataBad() {
const users = await fetch('/api/users').then(r => r.json());
const posts = await fetch('/api/posts').then(r => r.json()); // 待機中
const comments = await fetch('/api/comments').then(r => r.json()); // さらに待機
// 実行時間が3倍以上かかる可能性
}
PromiseとAsync/Awaitの使い分けガイド
よくあるハマりポイント:エラーハンドリングの違い
Promiseとasync/awaitではエラーハンドリングの方法が異なります。これを理解しないと、エラーが意図せず無視される状況が発生します。
// 【Promiseの場合】catchを忘れるとエラーが無視される
fetch('/api/data')
.then(r => r.json())
.then(data => console.log(data));
// .catch()がないと、エラーが検出されない
// 【async/awaitの場合】try-catchでキャッチ必須
async function getData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('エラーをキャッチ:', error);
}
}
// 【重要】HTTPエラーはawaitでは自動判定されない
async function safeGetData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('エラー:', error);
}
}
使い分けの判断基準
Promiseを使うべき場面:
- チェーン的な非同期処理の連結が多い場合
- Promise.race()やPromise.any()など高度なPromise操作が必要な場合
- 既存のPromiseベースのコードを拡張する場合
- ブラウザの互換性が極めて重要な場合(async/awaitはES2017以降)
async/awaitを使うべき場面(推奨):
- コードの可読性を最優先したい場合(ほとんどの実務シーン)
- 複数の非同期処理の制御が複雑な場合
- デバッグやログ出力を挟みたい場合
- try-catchでエラー処理を統一したい場合
- 同期的な思考フローで開発したい場合
実務的な併用パターン
// 【実践的なパターン】async/awaitとPromise.all()の組み合わせ
async function processUserData(userId) {
try {
// ユーザー情報は必ず先に取得
const userResponse = await fetch(`/api/user/${userId}`);
const user = await userResponse.json();
// その後の関連データは並列取得
const [posts, followers, settings] = await Promise.all([
fetch(`/api/posts?userId=${userId}`).then(r => r.json()),
fetch(`/api/followers?userId=${userId}`).then(r => r.json()),
fetch(`/api/settings?userId=${userId}`).then(r => r.json())
]);
return { user, posts, followers, settings };
} catch (error) {
console.error('データ取得失敗:', error);
throw error; // 上位で処理する場合
}
}
// リトライロジックの例
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error; // 最後の試行で例外を投げる
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); // 指数バックオフ
}
}
}
パフォーマンスの違いと最適化
JavaScriptエンジンレベルではPromiseとasync/awaitの性能差はほぼありません。両者ともEvent Loopで同じように処理されます。重要なのは「並列実行か順序実行か」という実装パターンです。
// 【パフォーマンス比較】
// ❌ 遅い:順序実行(3秒待機)
async function slowVersion() {
const a = await api1(); // 1秒
const b = await api2(); // 1秒
const c = await api3(); // 1秒
return { a, b, c }; // 計3秒
}
// ✅ 速い:並列実行(1秒待機)
async function fastVersion() {
const [a, b, c] = await Promise.all([
api1(),
api2(),
api3()
]); // 計1秒
return { a, b, c };
}
よくある質問
A: ほぼそうですが、完全ではありません。Promise.race()やPromise.any()などの高度な操作が必要な場合、Promiseを直接使う必要があります。また、Node.jsやブラウザのバージョンによってはasync/awaitが利用できない場合があります。
A: awaitは各反復で完了を待つため、順序実行になります。複数のPromiseを並列実行したい場合は、Promise.all()を使うか、先にすべてのPromiseを作成してから await Promise.all() で待つ必要があります。
A: async関数は自動的にPromiseを返すため、return文で値を返すだけでPromiseでラップされます。ただし、呼び出し側でPromiseとして扱う必要がある場合、明示的にPromiseを返すことを推奨します。
まとめ
- async/awaitはPromiseの糖衣構文であり、内部的には同じEvent Loopで動作している
- async/awaitはコード可読性が優れており、ほとんどの実務で推奨される
- 並列実行にはPromise.all()を組み合わせることで、パフォーマンスを大幅に向上できる
- エラーハンドリングはtry-catchで統一することが可読性を保つコツ
おすすめフロントエンドリソース
- MDN Web Docs The most trusted reference for HTML, CSS, and JavaScript.
- React Documentation Official React tutorials and API reference.
- Can I Use Essential tool for checking browser compatibility.