JavaScriptの非同期処理をシンプルかつ直感的に扱うために、async/await
構文が登場しました。
非同期処理は、現代のウェブアプリケーションにおいて必須の技術ですが、従来のコールバックやPromiseを使った方法では、しばしば複雑で可読性に欠けることがありました。async/await
はこれらの問題を解決し、コードをまるで同期処理のように書けるため、開発者の負担を大幅に軽減します。
本記事では、async/await
の基本から応用までを詳細に解説し、非同期処理のエラーハンドリングやベストプラクティスについても触れていきます。
さらに、実際のウェブアプリケーションにおける具体的なユースケースを通して、async/await
の力を最大限に引き出す方法を学びましょう。
目次
async/awaitの概要
async/awaitとは
async/await
は、直前の記事でご紹介した「Promise」を、よりシンプルに扱うための構文です。
async
キーワードを関数の前に付けることで、その関数が非同期関数として定義されます。await
キーワードを使用する(*1)と、Promiseが解決されるまで関数の実行を待機します。

*1:await
キーワードは、Promiseを返す関数呼び出しの前に付けて使用します。これにより、Promiseが解決(成功または失敗)されるまで関数の実行が一時停止されます。
await
キーワードは、Promiseを返す関数呼び出しの前に付けて使用します。await
はasync
キーワードで定義された非同期関数の内部でのみ使用できます。
Promiseとの関係
async/await
はPromiseの上に構築されています。これは、async/await
が内部的にPromiseを使用して非同期処理を管理していることを意味します。
具体的には、await
キーワードが使用されると、その非同期処理はPromiseとして扱われ、Promiseが解決されるまで関数の実行が一時停止されます。
従来のPromiseチェーンを使用する方法に比べ、async/await
を使うことでコードの可読性が大幅に向上し、エラーハンドリングも簡潔に行うことができます。
以下に、同じ処理をPromiseとasync/awaitで作成したサンプルを示しますので、比較してみてください。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('データ取得に成功しました');
}, 1000);
});
}
fetchData()
.then(data => {
console.log(data); // "データ取得に成功しました"
})
.catch(error => {
console.error('Error:', error);
});
👆上記の例では、関数本体がPromiseオブジェクトを返し、呼び出し側で then と catch のメソッドにより処理を繋げています。
これは、Promiseチェーンと呼ばれる仕組みです。
async function fetchDataAsync() {
try {
const data = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('データ取得に成功しました');
}, 1000);
});
console.log(data); // "データ取得に成功しました"
} catch (error) {
console.error('Error:', error);
}
}
fetchDataAsync();
👆上記の例では、fetchDataAsync
関数内で await
キーワードを使用してPromiseを待機しています。
この await
の動作は、then
メソッドを使って結果を処理するのと同じですが、コードがより直線的で可読性が高くなっています。
async/awaitの基本的な使用方法
基本的な構文と使用例
async/awaitの基本的な構文と使用例を示します。
async function fetchData() {
try {
const response = await fetch('https://not.existing.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Fetch error:', error);
}
}
fetchData();
👆上記サンプルプログラムの処理の流れは次の通りです。
await fetch('https://not.existing.com/data')
:- この行で
fetch
関数が呼ばれ、Promiseが返されます。 await
キーワードにより、Promiseが解決されるまで関数の実行が一時停止されます。- Promiseが解決されると、
response
に解決結果が格納されます。
- この行で
await response.json()
:response.json()
もPromiseを返すため、再びawait
でその解決を待ちます。- このPromiseが解決されるまで関数の実行が一時停止されます。
- Promiseが解決されると、
data
に解決結果が格納されます。
console.log(data)
:- 上記のすべての
await
処理が完了した後に、この行が実行されます。 console.log(data)
が実行される時点では、data
に非同期処理の結果が格納されています。
- 上記のすべての
何が嬉しいのか
時間のかかる処理を待たずして、先に次の処理を実行させられることです。
動作するサンプルで実例を示します。
<!-- ここより上は省略 -->
<body>
<script>
async function fetchData() {
try {
const response = await fetch('api/data.json');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Fetch error:', error);
}
}
fetchData();
console.log( "fetchData関数の結果よりも先に表示される" );
</script>
<!-- ここより下は省略 -->
fetchData関数の結果よりも先に表示される
▶ {testName: 'テスト'}
async/awaitのエラーハンドリング
非同期処理中にエラーが発生することは常に想定しておく必要があります。async/await
を使用することで、従来のPromiseチェーンに比べエラーハンドリングがより直感的で読みやすくなります。
エラーハンドリングについて、Promiseチェーンと async/await を比較すると以下のようになります。
- Promiseチェーンでは、
then
とcatch
を使って非同期処理とエラーハンドリングを行います。この方法でもエラーを一元的に処理できますが、複数の非同期処理がある場合、チェーンが複雑になることがあります。 - async/awaitでは、
await
とtry/catch
を使って非同期処理とエラーハンドリングを行います。非同期関数内でtry/catch
ブロックを使用することで、エラーハンドリングを同期コードのように書けるため、読みやすく管理しやすいコードになります。
async/awaitでのエラーハンドリング例
async function fetchData() {
try {
const response = await fetch('https://not.existing.com/data');
if (!response.ok) {
throw new Error('ネットワークエラーです。');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Fetch error:', error);
}
}
fetchData();
👆上記の例では、実際には存在していないURLにアクセスしており、意図的にエラーを発生させています。
そのため、動作させると if (!response.ok)
の判定が true
になり、Error
が投げられます。
よって、結果は以下のようになります。
▶ Fetch error: Error: ネットワークエラーです。
複数の非同期処理の実行
複数の非同期処理を並行して実行する場合、Promise.all
と async/await
を組み合わせることで効率的に処理できます。
Promise.all
についての詳細は、前回の記事『JavaScriptのPromise』の Promise.allの項 をご確認ください。
Promise.all と async/await を組み合わせた複数の非同期処理サンプル
async function fetchMultipleData() {
const urls = ['https://api.example.com/data1', 'https://api.example.com/data2'];
try {
const responses = await Promise.all(urls.map(url => fetch(url)));
const data = await Promise.all(responses.map(response => response.json()));
console.log(data);
} catch (error) {
console.error('Fetch error:', error);
}
}
fetchMultipleData();
👆上記のサンプルプログラムは少し難しいので、処理の流れを説明します。
- 非同期関数
fetchMultipleData
の定義:async
キーワードを使用して定義された非同期関数fetchMultipleData
内で、複数のURLからデータをフェッチする処理を行います。
- URLの配列を準備:
const urls = ['https://api.example.com/data1', 'https://api.example.com/data2'];
の部分で、フェッチするデータのURLを配列として定義します。
- 複数のフェッチリクエストを同時に実行:
const responses = await Promise.all(urls.map(url => fetch(url)));
の部分で、urls
配列内の各URLに対してfetch
関数を呼び出し、Promiseの配列を生成します。Promise.all
を使用しているため、これらのPromiseがすべて解決(または拒否)されるまで待機し、全てのリクエストが完了した後にresponses
に結果を格納します。
- レスポンスをJSON形式に変換:
const data = await Promise.all(responses.map(response => response.json()));
の部分で、各レスポンスオブジェクトのresponse.json()
メソッドを呼び出し、JSON形式のデータに変換します。
(response.json()
メソッドもPromiseを返すということです。)- 再度
Promise.all
を使用して、これらのPromiseがすべて解決されるのを待機し、変換後のデータをdata
に格納します。
- データの出力:
console.log(data);
の部分で、最終的に取得したデータをコンソールに出力します。
- エラーハンドリング:
try/catch
ブロックを使用して、非同期処理中に発生する可能性のあるエラーをキャッチし、console.error
でエラーメッセージを表示します。
このように、fetch
リクエストとその後のresponse.json
の処理を別々に行うことで、各フェッチリクエストが並行して実行され、その後並行してJSONに変換されます。
各ステップでエラーをキャッチしやすく、処理の途中でエラーが発生した場合に対処しやすいというメリットがあります。
async/awaitのまとめ
コードの可読性
async/await
を使用することで、非同期処理のコードが直線的で読みやすくなります。
複雑なPromiseチェーンよりも、エラーハンドリングやデバッグが容易です。
パフォーマンスの考慮
必要以上にawait
を多用しないことで、非同期処理のパフォーマンスを最適化できます。
並行して実行できる操作は、Promise.all
を使用して一度に処理しましょう。
アンチパターン
長いループ内でawait
を使用することは避け、代わりにPromise.all
を使用して並行処理を行うようにします。
本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひコメント頂ければ幸いです。
最後までお読みいただき、ありがとうございます。
コメント