JavaScriptのasync/awaitとは?基本的な使い方や処理方法をご紹介
非同期処理は現代のWEB開発で欠かせない要素ですが、実装は時として複雑になりがちです。そこで注目されているのが、JavaScriptのasync/await構文です。
本記事では、async/awaitの基本的な概念から実際の使用方法、さらにはPromiseとの比較や適切な使い分けまで、幅広く解説します。
async/awaitを使いこなせば、より読みやすく保守しやすい非同期処理のコードを書くことができるでしょう。
Contents
JavaScriptのasync/awaitとは
JavaScriptのasync/awaitは、非同期処理を簡潔に記述するための構文で、ECMAScript 2017(ES8)で導入されました。
従来の非同期処理では、コールバック関数やPromiseチェーンを使用していましたが、一文が長くなりがちでした。async/awaitは従来と比べ、コードがより読みやすくなり、保守性も向上します。
また、async/awaitを使うと、try-catch文を使って、同期処理と同じような方法でエラーをキャッチできるため、エラー処理がより直感的になります。
Promiseとの違い
基本的な非同期処理の仕組みはPromiseと同じですが、コードの見た目と書き方が大きく改善されています。
例えば、Promiseを使用した場合のコードは以下のようになります。
function fetchData() {
return fetch(‘https://api.example.com/data’)
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error(‘エラーが発生しました:’, error);
});
}
上記の例をみると、メソッドチェーンが連なり、全体で大きなreturn文となっています。
一方、async/awaitを使用すると、同じ処理を以下のように書けます。
async function fetchData() {
try {
const response = await fetch(‘https://api.example.com/data’);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(‘エラーが発生しました:’, error);
}
}
通常の同期処理と同じように個別の文として書けるため、処理の追加や変更がしやすく、例外処理も明確です。
async/awaitの基本的な使い方
本項では、async/awaitの基本的な使い方を見ていきましょう。
非同期関数の宣言
関数の前にasyncキーワードを付けて、非同期関数を宣言します。
async function fetchData() {
// 非同期処理
}
await演算子の使用
非同期処理の結果を待つ際にawaitキーワードを使います。
※ awaitは非同期関数でのみ使用できます。
async function fetchData() {
const response = await fetch(‘https://api.example.com/data’); // APIの応答を待つ
const data = await response.json(); // JSONとして解釈した結果を待つ
return data;
}
非同期関数の呼び出し
非同期関数は通常の関数と同じように呼び出せますが、結果はPromiseとして返されるため、非同期関数の外で結果を使うには、then()を使います。
fetchData().then(data => { // Promise.then()を使用してfetchData()の結果を処理
console.log(data);
});
エラー処理
try-catch文を使ってエラーを処理できます。
async function fetchData() {
try {
const response = await fetch(‘https://api.example.com/data’);
const data = await response.json();
return data;
} catch (error) {
console.error(‘データの取得に失敗しました:’, error);
}
}
async/awaitでの処理方法
async/awaitでは、さまざまな非同期処理を扱えます。
代表的な処理方法を見ていきましょう。
逐次実行
非同期処理を順番に実行する場合、以下のように記述します。
async function sequentialExecution() {
const result1 = await asyncFunction1();
const result2 = await asyncFunction2(result1);
const result3 = await asyncFunction3(result2);
return result3;
}
各処理が完了するまで次の処理を待機するため、処理時間は合計になります。
並列実行
複数の非同期処理を同時に実行する場合、Promise.all()を使用します。
async function parallelExecution() {
const [result1, result2, result3] = await Promise.all([
asyncFunction1(),
asyncFunction2(),
asyncFunction3()
]);
return { result1, result2, result3 };
}
並列実行では、処理時間は最も遅い処理の時間になるため、逐次実行よりも高速です。
タイムアウト処理
指定した時間内に処理が完了しない場合、Promise.race()を使用し、エラーにできます。
async function timeoutExecution(ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(‘Timeout’)), ms) // 指定時間ms後エラーを発生する
);
try {
return await Promise.race([asyncFunction(), timeout]); // 一番早く成功・失敗した状態に決定する
} catch (error) { // 時間までにtimeout以外のプロミスが成功しないと、エラーとなる。
console.error(‘時間内に処理が完了しませんでした:’, error);
}
}
非同期ループ
配列の要素に対して非同期処理をおこなう場合、for…of文とasync/awaitを組み合わせます。
async function asyncLoop(items) {
const results = [];
for (const item of items) { // itemsの各要素に対して順番に
const result = await asyncFunction(item); // 非同期処理の結果を待つ
results.push(result); // 処理結果を配列resultに追加する
}
return results;
}
async/awaitとPromiseを使い分ける方法
async/awaitとPromiseを適切に使い分けると、非同期処理コードの可読性や保守性を向上させられます。
本項では、使い分けの目安となるポイントを見ていきましょう。
処理の複雑さで選ぶ
単純な非同期処理の場合はPromiseを使用し、複雑な処理や複数の非同期処理を連続しておこなう場合はasync/awaitを使用するのが一般的です。
単純な例
function fetchUserData(userId) { // 単純な例:Promiseを使用する
return fetch(`https://api.example.com/users/${userId}`) // APIにアクセス
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(‘Error:’, error));
}
複雑な例
async function fetchUserAndPosts(userId) {
try { // 複雑な例:複数のAPIの結果を処理するケース async/awaitを使用
const userData = await fetch(`https://api.example.com/users/${userId}`).then(res => res.json());
const userPosts = await fetch(`https://api.example.com/users/${userId}/posts`).then(res => res.json());
const lastPostComments = await fetch(`https://api.example.com/posts/${userPosts[0].id}/comments`).then(res => res.json());
console.log(‘ユーザーの投稿・コメント:’, [userData, userPosts, lastPostComments]);
} catch (error) {
console.error(‘Error:’, error);
}
}
処理性能で選ぶ
async/awaitは内部でPromiseを使用しているため、性能に大きな差はありません。
ただし、非同期処理を大量に並列で実行する場合、Promise.allなどを使うほうが効率的な場合もあります。
function fetchMultipleUsers(userIds) { // userIds: 多数のIDを格納した配列
const promises = userIds.map(id => fetch(`https://api.example.com/users/${id}`).then(res => res.json()));
return Promise.all(promises) // userIdsの要素数だけ並列で処理する
.then(users => console.log(‘ユーザー一覧:’, users))
.catch(error => console.error(‘Error:’, error));
}
読みやすさで選ぶ
コードの可読性も重要な要素です。
async/awaitを使用した、同期処理のようなコードは理解しやすいです。
async function processOrder(orderId) { // 注文を処理する関数
try {
const order = await fetchOrder(orderId); // 注文情報の取得を待つ
const user = await fetchUser(order.userId); // ユーザー情報の取得を待つ
const payment = await processPayment(order.total, user.paymentMethod); // 決済処理を待つ
const shipment = await createShipment(order, user.address); // 配送データ作成を待つ
console.log(‘注文は正常に処理されました:’, { order, payment, shipment });
} catch (error) {
console.error(‘注文処理エラー:’, error);
}
}
一方で、Promiseチェーンを使うと、処理の依存関係を理解しやすい場合もあります。
function processOrder(orderId) {
return fetchOrder(orderId)
.then(order => fetchUser(order.userId).then(user => ({ order, user })))
.then(({ order, user }) => processPayment(order.total, user.paymentMethod).then(payment => ({ order, user, payment })))
.then(({ order, user, payment }) => createShipment(order, user.address).then(shipment => ({ order, payment, shipment })))
.then(result => console.log(‘注文は正常に処理されました:’, result))
.catch(error => console.error(‘注文処理エラー:’, error));
}
まとめ
async/awaitは、ECMAScript 2017で導入された非同期処理のための構文です。Promiseよりも直感的に書けるため、コードの理解や保守が容易になります。逐次実行、並列実行、タイムアウト処理など、さまざまな処理方法に対応できる柔軟性も魅力です。
非同期処理の効率化に悩んでいる方は、ぜひasync/awaitの導入を検討してみてください。