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を使用して並行処理を行うようにします。
本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひコメント頂ければ幸いです。
最後までお読みいただき、ありがとうございます。

コメント