非同期処理とは?
非同期処理とは、一連の処理が終了するまで待たずに、他の処理を実行するプログラミング手法のことです。
例えば、画面の描画をしている最中に、サーバー側からDBのデータを持ってくる等が非同期処理にあたります。
このように、必ずしも処理が終わるまで待たなくても、同時並行で別の処理をさせることで、
パフォーマンスが向上します。これが非同期処理のメリットだと言えます。
ただし、処理が複雑になり、データの流れが追いづらくなるというデメリットもあります。
TypeScriptを使用する意味
非同期処理の機能は、JavaScriptも持っています。
非同期処理のデメリットを緩和できるものが、TypeScriptです。
TypeScriptは、JavaScriptの上位互換である言語のため、当然、非同期処理の機能を持ちます。
プラスで、型注釈などの便利な機能の恩恵で、非同期処理のデメリットを緩和できるという訳です。
これがTypeScriptを使用する意味です。
TypeScriptの恩恵
TypeScriptを使うことで得られる恩恵は、たくさんあります。
| 恩恵 | 説明 |
| 型安全 | 型注釈を利用することで、型の不一致がある場合にコンパイルエラーになります。 そのため、想定外のバグを事前に検知できる可能性が高くなります。 |
| 補完機能 | 型情報が明確な場合、IDE側で型の推測が可能になり、予測変換の幅が広がります。 |
| 理解度の向上 | 型注釈を利用することで、その関数が何者か判別しやすくなります。 よって、コードの理解度が速くなります。 |
Promise
非同期処理の基本的な概念の一つに、Promise(プロミス)があります。
Promiseは、非同期操作の結果を表すオブジェクトです。
そして、処理の終わりに結果を返すことを約束しています。
この約束は、処理が成功か失敗に関わらず、結果を返します。
Promiseは、以下の3つの状態を持つことができます。
| 状態 | 説明 |
| Pending(未決) | 処理が進行中で、結果がまだ返ってきていない状態 |
| Fulfilled(履行) | 処理が成功し、結果を利用できる状態 |
| Rejected(拒否) | 処理が失敗しエラーが発生している状態 |
Promiseのサンプルコード
Promiseは、then()メソッドを持っており、
非同期処理の結果を受け取るためのコールバック関数を登録するために使用します。
実際に使用してみます。
// 非同期処理を行う関数
function fetchData() {
return new Promise((resolve, reject) => {
// ここで非同期処理を行う
// 処理が成功したら resolve を呼び出し、失敗したら reject を呼び出す
})
}
// fetchDataを呼び出す
fetchData()
.then(data => {
// 非同期処理が成功した場合に実行される
console.log(data)
})
.catch(error => {
// 非同期操作が失敗した場合に実行される
console.error("エラー検知:", error)
})このように、then()メソッドを用いて、
受け取ったデータの処理や、エラーが発生した際の処理を分岐させることが可能です。
次に、4行目と5行目の処理を具体的に書いたサンプルコードを紹介します。
下記4行目のfetch関数は、Promiseを返す組み込み関数です。
fetch関数を使用することで、指定したURLからのデータを受け取ることできます。
そのデータにthen()メソッドを使ってデータにアクセスします。
function fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(response => {
if (!response.ok) {
// サーバからエラーレスポンスが返された場合
// (404などの200番台以外のエラーコード)
throw new Error('Network response was not ok')
}
// レスポンスをJSONとしてパース(解析)する
return response.json()
})
.then(data => {
// データの取得に成功したので、resolveを呼び出す
resolve(data)
})
.catch(error => {
// エラーが発生したので、rejectを呼び出す
reject(error)
})
});
}
// 仮のURLを作成
const url = 'https://example.com/sample-data'
fetchData(url)
.then(data => console.log(data)) // 成功時の処理
.catch(error => console.error(error)); // 失敗時の処理response.okって何?
まず、responseは、非同期処理が成功したときに受け取れる値の名前です。
つまり、なんでもいいです!(めんどくさいので、resとかにしたりします)
そして、okは、responseオブジェクトのプロパティです。(これは名前変えられない)
戻り値は、boolean型になります。
つまり、サーバーからのステータスコードが200番台であればtrueを返します。
逆にそれ以外(404や500など)なら、falseを返します。
resolveとrejectって何?
resolveとrejectは、プログラマーが勝手につけた名前であり特に意味はありません。
サンプルコードの2行目で定義したものが、resolveとrejectだったというだけです。
ですので、onSuccessとonFailureでも構いません。
本当に大切なのは、名前ではなく、何をしているかです。
サンプルコードの2行目で定義したresolveは、Promiseが成功したことを意味します。
つまり、通信が上手くできたから、APIから返却された値を返しているという意味になります。
サンプルコードでは、JSONとしてパースされた値を成功した保証書として返却しています。
次に、rejectですが、これはPromiseが失敗したことを意味します。
resolveの逆で、失敗した保証書としてエラー情報を返却しています。
このエラー情報には、様々なデータが含まれており、
エラーの把握や解決に役立ちます。
リクエストとしてパラメータを渡す場合は?
まず、サーバーへのリクエストは、サンプルコードのfetch(url)で送信しています。
fetch(url)fetchはPromiseを返し、そのPromiseは非同期処理が完了すると解決します。
この場合はGET送信になります。
そこで、リクエストにパラメータ(情報)を付与した状態で渡したい場合は、
以下のように修正します。
GET送信の場合
GET送信の場合は、searchParamsプロパティを使用して値を付与します。
注意点としては変数urlを定義する際にnewをしないと、searchParamsは使用できません。
url = new URL('https://example.com/sample-data') // 新たにURLオブジェクトを生成
url.searchParams.append('param1', 'value1')
url.searchParams.append('param2', 'value2')
console.log(url.toString()) // https://example.com/sample-data?param1=value1&param2=value2
// fetchでレスポンスの受け取り
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error))POST送信の場合
POST送信の場合は、オブジェクトを作成してリクエストボディにパラメータを渡します。
const url = 'https://example.com/data'
const data = {
param1: 'value1',
param2: 'value2'
};
fetch(url, {
method: 'POST', // HTTPメソッドをPOSTに指定
headers: {
'Content-Type': 'application/json', // コンテンツタイプをJSONに指定
},
body: JSON.stringify(data), // ここでデータをJSON形式の文字列に変換
})
.then(response => response.json()) // レスポンスを解析し、次のthenで受け取れる形に変換
.then(data => console.log(data)) // 成功した場合に、dataにレスポンスが入る
.catch(error => console.error(error)) // 失敗した場合に、エラーハンドリングをするasyncとawait
次は、asyncとawaitについて解説します。
この2つは、ES2017で導入され、
現在のJavaScriptやTypeScriptでは一般的に使用されています。
asyncとは?
asyncキーワードを関数の前につけると、その関数は非同期関数として認識されます。
また、return文がある場合、その値は自動的にPromise.resolveにラップされます。
async function asyncFunc() {
return "Hello, World!"
}
asyncFunc().then(message => console.log(message))
// Hello, World!awaitとは?
awaitは、非同期関数の中でのみ使用できます。
下記のサンプルコードでは、fetchによって返されるPromiseを待ってから、
非同期処理を行うという意味になります。
下記のサンプルコードでは、通信に成功してJSONにパースされた値が、
返却され、asyncFunc()で非同期関数を呼び出した後、then()メソッドのdataという
変数で値を取得することができます。
async function asyncFunc() {
const response = await fetch("https://example.com/sample-data")
const data = await response.json()
return data
}
asyncFunc().then(data => console.log(data))また、3行目でawaitを使っているので、7行目の最後にcatchを付けて、
そこでエラーハンドリングをすることも出来ます。
エラーハンドリング
asyncとawaitを使ったエラーハンドリングのサンプルコードを紹介します。
awaitを使用することで、Promiseが拒否されると拒否結果を返します。
そのため、try-catch文を使用してエラーハンドリングをすることが可能です。
async function asyncFunc() {
try {
const response = await fetch("https://example.com/data");
const data = await response.json()
// 成功した時にdataを返却
return data
} catch (error) {
console.error(error)
// 拒否された時にnullを返却
return null
}
}
asyncFunc().then(data => {
if (data !== null) {
console.log(data)
}
})今回は、サンプルコードなので、結果をコンソールに表示しているだけですが、
自由に拡張することができます。
例えば、利用者がエラーに気づけるように、アラートを表示したりすることも可能です。
TypeScriptで非同期処理
最後に、非同期処理のサンプルコードをTypeScriptバージョンで書いてみます。
TypeScriptはJavaScriptの上位互換ですので、
Promiseやasyncやawaitの概念はそのまま使えます。
変化点としては、型注釈を用いることで安全性が向上する点です。
まずは、基本的な宣言からしてみます。
戻り値は、Promise<string>です。
async function fetchData(): Promise<string> {
let response = await fetch('https://example.com/sample-data')
let data = await response.text()
return data
}上記の非同期関数を呼び出して、結果をコンソールに表示するためには、下記のコードを追加します。
fetchData().then(console.log)エラーハンドリングをする場合は、非同期関数を下記のように書き換えます。
async function fetchData(): Promise<string> {
try {
let response = await fetch('https://example.com/sample-data')
let data = await response.text()
return data
} catch (error: any) {
console.error(error)
throw error // エラーを再スロー
}
}エラーがあった場合に、catchされるので、
catchのスコープ内に、どのような対応をするかプログラマーが設定することができます。
まとめ
非同期処理は、一連の処理の終了を待たずに、他の処理をするプログラミング手法の1つです。
非同期処理をするためには、Promiseやasync/awaitを使って非同期関数を作ります。
TypeScriptを使用することで、非同期処理の安全性が向上したり、可読性が向上します。
かなり複雑な概念ですので、慣れるまでに時間がかかりますが、
慣れてしまえば便利すぎて手放せなくなります!
ここまで、読んでいただき、ありがとうございます。


コメント