はじめに
業務で使用していた一般的な非同期通信(async/await
)を、Angularの非同期通信に変更する実装がありました。「普通の非同期通信とAngularの非同期通信はどう違うのか」という疑問が湧いた方もいるのではないでしょうか。本記事では、Angularの非同期通信に関してまとめています。
非同期通信とは
非同期通信とは、プログラムが時間のかかる操作(例えば、ネットワークリクエストやファイルの読み書き)を行う際に、その処理が完了するのを待たずに他の作業を進めることができるようにする方法です。これにより、アプリケーションの応答性が向上し、ユーザーは操作の遅延を感じにくくなります。例えば、APIからデータを取得している間にも、ユーザーは他の機能を使い続けることができるため、全体的なユーザー体験が向上します。
一般的に、非同期通信は「バックグラウンドでの処理」と捉えることができ、例えばAPIからデータを取得している間に他のタスクを進めることができるという利点があります。JavaScriptでは、Promiseやasync/awaitがこの非同期処理を実現するための主要な手段です。
一般的な非同期通信とは
まず、JavaScriptにおける「普通の非同期通信」というと、async/await
やPromise
を使用した通信方法を使用しています。
例として、APIからデータを取得するシンプルな関数を用意しました。
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
};
このコードでは、fetch
関数を使用してAPIからデータを取得し、それをawait
を使って待つことで、コードの流れを同期的に見せつつも非同期処理を実行しています。
Angularの非同期通信とは
Angularは、フレームワークとして非同期通信に特化した機能を提供しています。特に、AngularのHttpClient
モジュールを使用することで、APIリクエストを行う際にObservable
を活用することができます。
Angularにおける非同期通信の例として、以下のコードを見てみましょう:
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
constructor(private http: HttpClient) {}
fetchData(): Observable<any> {
return this.http.get('https://api.example.com/data');
}
AngularのHttpClientを使用すると、getメソッドがObservableを返します。Observableは、リアクティブプログラミングの一部で、データのストリームを扱うための強力なツールです。データのストリームとは、データが逐次的に提供される流れのことで、例えばリアルタイムでのデータ更新や、ユーザーのインタラクションに応じたデータ取得などが含まれます。この方法は、データの処理において非常に柔軟で、ストリーム内のデータに対して複数の操作を行うことが可能です。
Observable
とPromise
の違い
Observable
とPromise
の違いについて簡単に説明します。
- Promise: 一度きりの非同期操作の結果を扱います。成功または失敗のいずれかが返され、その後は変更されません。
- Observable: 複数回のデータの発行を扱うことができます。データの変更や新しいデータの追加にリアクティブに対応可能で、データのストリームに対する複雑な操作を行うことができます。
Angularでは、特に複雑な非同期処理や複数のデータの流れを管理する必要がある場合にObservable
を利用することで、データの変化にリアクティブに対応することができます。
pipe、mergeMap、concatMap、switchMapなどの演算子について
AngularでObservableを使用する際、pipe、mergeMap、concat、concatMap、switchMap、subscribeといったrxjsの演算子をよく使用します。これらは非同期処理を効率的に行うための強力なツールで、データの流れを操作したり、APIリクエストを連携させたりするのに役立ちます。
-
pipe: Observableに対して複数の演算子を適用するための関数です。複数の処理をチェーンすることで、コードの可読性が向上します。
-
mergeMap: 複数の非同期操作を順次実行したり、並列で実行する場合に使用されます。特に複数のAPIリクエストを扱う際に便利です。
-
concatMap: 各非同期操作を順次実行しますが、mergeMapとは異なり、前の操作が完了するまで次の操作を開始しません。そのため、各操作が確実に順番に実行されることを保証したい場合に使用します。
-
switchMap: 新しいObservableが発行されると、以前のObservableの処理をキャンセルし、最新のリクエストのみを処理します。例えば、ユーザーの入力に応じて検索結果を取得する場合に便利で、古いリクエストの結果が不要な場合に使用されます。
-
concat: 複数のObservableを順次結合して、一つのObservableとして扱います。
-
subscribe: Observableから発行されたデータを受け取るために使用します。データの取得やエラーハンドリング、完了時の処理などを行います。
演算子を使った具体例
pipe
やmergeMap
を使った具体的になります
例えば、APIからユーザー情報を取得し、そのユーザーの投稿を取得する処理を行う場合、以下になります。
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
constructor(private http: HttpClient) {}
fetchUserAndPosts(userId: string): Observable<any> {
return this.http.get(`https://api.example.com/users/${userId}`).pipe(
mergeMap((user: any) => this.http.get(`https://api.example.com/users/${user.id}/posts`))
);
}
このコードでは、まずユーザー情報を取得し、その後にmergeMap
を使ってユーザーの投稿を取得しています。mergeMap
を使用することで、最初のリクエストが完了した後に次のリクエストを実行し、効率的に複数の非同期操作をつなげることができます。
concatMap
の使い方
concatMap
を使用することで各リクエストが順次実行されることを保証できます。
fetchUserAndPostsSequentially(userId: string): Observable<any> {
return this.http.get(`https://api.example.com/users/${userId}`).pipe(
concatMap((user: any) => this.http.get(`https://api.example.com/users/${user.id}/posts`))
);
}
この例では、concatMap
を使用することで、最初のリクエストが完了してから次のリクエストを実行します。
この例では、map
演算子を使用して投稿データからタイトルだけを抽出しています。pipe
を使うことで、データの流れに対して複数の操作をチェーンして適用することができます。
switchMap
の使い方
fetchUserPosts(userId: string): Observable<any> {
return this.http.get(`https://api.example.com/users/${userId}`).pipe(
switchMap((user: any) => this.http.get(`https://api.example.com/users/${user.id}/posts`))
);
}
このコードでは、新しいユーザーIDが渡されるたびに、前のリクエストがキャンセルされ、最新のリクエストのみが実行されます。
subscribe
の使い方
Observable
を使用して非同期処理を行う場合、最終的にsubscribe
を呼び出してデータを取得します。例えば、以下のようにしてデータを受け取ります:
this.fetchUserAndPosts('123').subscribe(
data => {
console.log('User posts:', data);
},
error => {
console.error('Error:', error);
}
);
subscribe
を使用することで、Observable
から発行されたデータを実際に受け取り、表示したり他の処理を行ったりできます。
これらの演算子を理解し、使いこなすことで、Angularにおける非同期通信の柔軟性を活用し、効率的なコードを書くことができます。
forkJoin
を使用することで非同期処理と同期処理を組み合わせる
以前実装で非同期と同期処理を組み合わせて実装していた際に複数の非同期処理を個別に行い、それぞれの結果が揃う前に次の処理を進めてしまいエラーが起きてしまうことがありました。
その場合にforkJoin
を使用してすべての非同期リクエストが完了するのを待ち、揃ったデータを使って次の処理を行うことで処理の順序による不具合を防ぐことができます
import { forkJoin, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
export class UserDataService {
constructor(private http: HttpClient) {}
public getUserDetails(extraData: any): void {
// 非同期処理をまとめる
forkJoin({
userInfo: this.getUserInfo(this.userId).pipe(catchError(() => of('ユーザー情報の取得に失敗しました'))),
userPosts: this.getUserPosts(this.userId).pipe(catchError(() => of('ユーザー投稿の取得に失敗しました')))
}).subscribe(({ userInfo, userPosts }) => {
// 以降に同期的な処理を書く
// 受け取ったデータと非同期で取得したデータを組み合わせた新しいオブジェクトを作成
const combinedData = {
extraData,
userInfo,
userPosts
};
// 例えば新しいオブジェクトを何かに渡すことなどができます。
console.log('統合されたデータ:', combinedData);
});
}
// ユーザー情報取得
private getUserInfo(userId: number) {
return this.http.get(`/api/users/${userId}`);
}
// ユーザー投稿取得
private getUserPosts(userId: number) {
return this.http.get(`/api/users/${userId}/posts`);
}
}
なぜAngularの非同期通信が提案されたのか
Angularの非同期通信を提案された理由として、以下の点が考えられます:
-
リアクティブなデータ管理: Angularの
Observable
はデータの変化をリアルタイムに反映するのに適しています。これにより、UIとデータの整合性を簡単に保つことが可能です。 -
ストリーム操作の柔軟性:
rxjs
ライブラリを使うことで、データのフィルタリングや変換といった操作が非常に容易になります。複数のAPIリクエストを連携させる場合など、コードがシンプルになります。 -
Angularのエコシステムとの親和性: Angularはリアクティブプログラミングの思想に基づいて設計されており、
Observable
の使用が推奨されています。これにより、Angularの他の機能(例えばフォームやルーティング)との統合がスムーズに行えます。
まとめ
一般的な非同期通信(async/await
)はシンプルで分かりやすく、多くの場面で使いやすいですが、Angularの非同期通信(Observable
)はデータのリアクティブな管理や複雑なストリーム操作において優れています。Angularのプロジェクトにおいては、HttpClient
とObservable
を利用することで、コードの一貫性を保ちつつ、リアクティブなデータ管理が可能になります。
以上、Angularの非同期通信に関して、まとめてみました。参考になりましたら幸いです。