前回の記事でPromiseを使った非同期処理を学びました。
今回はさらにそのPromiseを簡単に扱えるようにするための新しい方法「async」と「await」について解説します。
これを使えば非同期処理がもっと直感的に書けるようになります。
初心者のかたも少しずつ理解していきましょう。
asyncとawaitの基本
asyncとは?
asyncは関数を非同期関数として宣言するためのキーワードです。
非同期関数は常にPromiseを返します。
asyncを使うことで、関数内で非同期処理を行うことができます。
以下に使用例を掲載します。
async function fetchData() {
return "データが取得されました!";
}
fetchData().then(data => {
console.log(data); // データが取得されました!
});
awaitとは?
await はPromiseが解決(成功)または拒否(失敗)されるまで待つためのキーワードです。
awaitはasync関数内でしか使えません。
以下に使用例を掲載します。
async function fetchData() {
const data = await new Promise(resolve => {
setTimeout(() => resolve("データが取得されました!"), 2000);
});
console.log(data);
}
fetchData();
(2秒後)
データが取得されました!
補足:ここではまだ「ふ~ん」で大丈夫です。
asyncとawaitを使った非同期処理の書き方
非同期処理の流れ
従来のPromiseを使った非同期処理は、thenやcatchで処理を繋げていく必要がありました。
しかしasyncとawaitを使えば、同期処理のように書くことができます。
以下に使用例を掲載します。
まずは従来のPromiseを使ったコードです。
function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve("データが取得されました!"), 2000);
});
}
fetchData().then(data => {
console.log(data);
});
次にasyncとawaitを使ったコードです。
async function fetchData() {
const data = await new Promise(resolve => {
setTimeout(() => resolve("データが取得されました!"), 2000);
});
console.log(data);
}
fetchData();
補足:ここでもまだちょっとわかりづらいですね。
エラーハンドリング(エラー発生時の処理)
async関数の中でエラーが発生した場合、そのエラーはcatchブロックで処理することができます。
awaitで待っている間にエラーが発生した場合も同様です。
async function fetchData() {
try {
const data = await new Promise((resolve, reject) => {
setTimeout(() => reject("データの取得に失敗しました..."), 2000);
});
console.log(data);
} catch (error) {
console.error(error);
}
}
fetchData();
(2秒後)
データの取得に失敗しました...
補足:つまり???
非同期処理ではasyncとawaitを使ったほうが良い理由5選
教科書っぽくここまで解説してきましたが、非同期処理ではpromiseを直接使うのではなく、asyncとawaitを使ったほうが良い理由がいまひとつよくわかりません。
そのため以下にまとめます。
共通して言えることは「コードが読みやすくなってメンテナンスしやすい」ということです。
共通部分
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("データを取得しました!");
}, 2000);
});
}
function processData(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data + " 処理完了!");
}, 2000);
});
}
Promiseを使ったコード
fetchData()
.then((data) => {
console.log("データ取得中...");
return processData(data);
})
.then((processedData) => {
console.log(processedData);
})
.catch((error) => {
console.error("エラーが発生しました: ", error);
});
asyncとawaitを使ったコード
async function handleData() {
try {
console.log("データ取得中...");
const data = await fetchData();
const processedData = await processData(data);
console.log(processedData);
} catch (error) {
console.error("エラーが発生しました: ", error);
}
}
handleData();
補足:文字数はほぼ同じよね。
そういえば()の数が少ないかしら?
- コールバック地獄を避ける
Promiseを使うと、thenチェーンが増えるにつれて、コードがネストしやすくなり、いわゆる「コールバック地獄」に陥ることがあります。
async/awaitではそのようなネストがなく、シンプルに順序立てて書けるため、複雑さが大幅に減少します。
2つ目のthenに値を渡すため、1つ目のthen内でreturnを記述しなければならないなど、いろいろと面倒なのです。 - エラーハンドリングが簡単
async/awaitを使うことで、従来の同期処理と同じようにtry…catchブロックでエラーをキャッチできます。
これによりエラーハンドリングが直感的かつ一貫性のあるものになります。
複数のPromiseを連結する場合、Promiseチェーンでのエラーハンドリングは記述が散らばりやすくなりますが、async/awaitでは1箇所でまとめて記述・処理できます。 - デバッグがしやすい
async/awaitは同期処理のように記述されるため、デバッガでステップ実行しやすく、非同期処理の流れを追いやすくなります。
デバッガとは動いているプログラムを一時的に止めたり、実行途中のデータの中身を好きな値に書き換えたり、プログラムの不具合の原因を探す上で便利な機能を提供してくれる仕組みです。
Promiseチェーンの場合、ステップを追う際にどのthenにいるかがわかりにくいことがありますが、async/awaitではその心配がありません。 - 同期処理に近い感覚
非同期処理が多くなると、処理の流れが複雑になりがちですが、async/awaitを使うことで同期処理に近い感覚でコードを書けるため、流れが明確になります。 - メンテナンスが容易
以上のことから、コードが読みやすくなることで、他の開発者や未来の自分がコードを見た際に理解しやすくなり、メンテナンスが楽になります。
過去の自分と意思疎通が図れず、書いてあることが理解できないなんて日常茶飯事です…
特に大規模なプロジェクトでは、コードの可読性が高いことが、バグの発生率を下げ、修正を容易にします。
まとめ
async/awaitを使うことで、Promiseの複雑さを取り除き、コードを読みやすく、メンテナンスしやすいものにできます。
特に複雑な非同期処理が絡む場合、その利便性は非常に大きいです。
これから新しいコードを書く際には、async/awaitを優先して使用することを強くお勧めします。