Angular は優れたフレームワークですが、非同期なコードを書いたり、非同期な呼び出しをするのは簡単ではありません。特に、Async/Await とAngular Promiseのどちらを使うか決めかねている場合はなおさらです。そこで、このチュートリアルでは例とコードスニペットを通して Async/Await と Promise の違いを理解していただけるようお手伝いをします。また、コールバック地獄を回避する方法を説明し、Ignite UI for Angularの助けを借りて、JavaScript で Async/Await を使用する方法を実演します。
この記事の原文は以下よりご確認いただけます。
Svetloslav Novoselski / Monday, May 9, 2022
Angular Async/Await: Why You Needed It & How To Use It
- Angular の非同期コードとは?
- Async/Await と Promise: 違いは何ですか?
- Ignite UI でAngular の Async/Await を使用する方法
- Async/Await Angular のベストプラクティス
- まとめ
Angular の非同期コードとは?
一般的に非同期コードはコードが順次で実行されず、マルチスレッドを含む処理であることを意味します。これは並列プログラミングの一種で、主となるアプリケーションのスレッドを中断することなく、コードの別のブロックを実行するものです。非同期コードの実行は、大規模な反復処理を行う場合や、複雑な処理を行う場合に有効です。
しかし、Angular アプリでシンプルさと実行時間の短縮を目指すのであれば、非同期プログラミングは避けた方がよいでしょう。通常、非同期コードは読みにくいので、開発者にとって読みやすく、よりシンプルにすることができれば、それは質の高いものとみなされます。そして、JavaScript にはこれに対処するための独自のトリックとメカニズムがあります。
JavaScript による非同期コードの扱い方
デフォルトでは JavaScript は同期型のシングルスレッド言語であり、上から下に向かって一度に1つのステートメントを実行します。つまり、新しいスレッドやプロセスはメインプログラムの流れと並行してコードを実行することができません。コールスタックとヒープメモリを1つずつ持ち、コードを順番に実行し、あるコードの実行を終えてから次のコードに移らなければなりません。すべてが順番に行われます。しかし、例えば1つのページで複数の AJAX 呼び出しを行う必要がある場合はどうなるでしょうか。
最初に思いつく解決策はコールバックの使用です。しかし、これはプログラマーの間で「コールバック地獄」として知られる大きな問題を引き起こします。基本的に、これは入れ子になった promise-then のコード構造のことで、複数のリクエストとデータ変換が必要となり、しばしばコードのメンテナンスが難しくて読みにくく、スケーラブルでないアプリになることがあります。
コールバックは Angular アプリの非同期コードを処理するのに最適な方法ではないことがわかりました。ではどうするのでしょうか? Angular Promise はどうでしょう?このパターンは非同期処理を効率的にラップし、完了したときに通知します。しかし、よりクリーンで保守性の高いコードとなったとしても、Angular Promise を使うことは最適な解決策ではありません。なぜなら、同じコードを何度も再利用し、DRY (Don't Repeat Yourself) 原則に反しているからです。
ありがたいことに、Angular には Async/Await があります。
Angular で Async/Await を使用する
JavaScript の最も優れた改良点の1つは、ECMAScript 7 で導入された Async/Await 機能です。基本的に Async/Await は Promise の上で動作し、非同期コードを同期的に記述することができます。コードがシンプルになり、フローやロジックがより理解しやすくなります。
また、then と catch の連鎖を使わなくなったので、try/catch を実行することでエラーを処理できます。
Async/Await と Promise: 違いは何ですか?
Async/Await と Promise の違いを示すために、より消化しやすく簡潔な方法で比較してみましょう。
Promise | Async/Await |
---|---|
Promise は未来のある時点で実行が完了することが保証されている操作です。 | Async/Await は Promise の上に構築されています。Promise の糖衣構文であり、コードをより同期的に見せるものです 。 |
エラー処理は then() メソッドと catch() メソッドで行います。 | エラー処理は try() メソッドと catch() メソッドで行います。 |
Promise チェーンを理解するのが難しい場合があります。 | AsyncとAwaitによってコードが読みやすくなり、プログラムの流れが理解しやすくなります。 |
pending、resolved、rejected の3つの状態があります。 | resolved または rejected された Promise を返します。 |
両方の構文を使用した例を作成し、その違いを見てみましょう。API に対して2つの非同期呼び出しを行う関数があるとします。1つはユーザーデータを取得するため、もう1つはユーザーがメンバーであるクラブを取得するための関数です。2 番目の呼び出しは最初の呼び出しに依存します。つまり、getUserData() を完了させてから、もう一方のリクエストを実行する必要があります。どちらの構文も読みやすいですが、当然ながら .then() と .catch() で Promise を使うとコールバック地獄になり、コードの理解やメンテナンスが難しくなります。
const makeRequest = () => getUserData() . then(user => getClubsForUser(user.id)) .then(console.log) .catch(err => console.log('Error: ' + err.message));
const makeRequest = async () => { try { let user = await getUserData(); let clubs = await getClubsForUser(user.id); console.log(clubs); } catch(err) { console.log(err.message); } };
Ignite UI でAngular の Async/Await を使用する方法
フォーム入力からユーザーデータを取得し、API にリクエストしてユーザーの認証情報が正しいかどうかを確認し、正しい場合はホームページにリダイレクトする関数を作成しましょう。Ignite UI for Angularの Async/Await を使用することでこのような状況に対応することができます。API へのリクエストは非同期処理なので await を使い、他に問題がなければデータを保存するようにします。
const submitLoginData = async () => { try { if (this.userInput.email && this.userInput.password) { const response = await this.loginUser(this.userInput); if (response.statusCode === 200) { alert('User is successfully logged in!'); await saveUserData(response.data); this.router.navigate(['/']); } else { alert(response.message); this.router.navigate(['/login']); } } } .catch(err) { alert('Something went wrong, try again later!') this.router.navigate(['/login']); } }
ここでは .then() と .catch() を使うこともできますが、Ignite UI for Angular の Async/Await はコードをよりエレガントに見せてくれます。どの構文を使うかはコードを書く人次第です。ただし、スタイルを混在させるとコードが複雑になるので、一つのアプローチに統一することをお勧めします。
Async/Await Angular のベストプラクティス
async や await を使って非同期コードを処理する場合、必ず try/catch ブロックにコードを記述する必要があります。こうすることで、たとえ Promise がエラーを投げても、それをキャッチして適切に処理することができます。
async function loadComponents() { try { this.components = await fetchComponentsByType('charts'); } .catch(err) { logger.error(err); } }
Ignite UI では、非同期関数の中で明示的に Promise を返す必要はありません。実際、非同期関数は常に Promise を返します。つまり、非同期関数内で Promise を作成しても、Angular アプリのパフォーマンスのオーバーヘッドが増えるだけです。
async function loadChartSelectors() { return Promise.resolve(['igx-data-chart', 'igx-pie-chart', 'igx-category-chart']); }
まとめ
Async/Await キーワードはコードを読みやすく、デバッグしやすくします。しかし、それらを正しく使いたいのであれば Promise が一般的にどのように動作するのかを理解する必要があります。なぜなら、ここまで述べたように、それらは Promise の糖衣構文に過ぎないからです。