【TypeScript勉強記】:(自分用)非同期処理で型定義
学習素材
【日本一わかりやすいTypeScript入門】非同期処理にも型定義!APIから安全にデータを取得
非同期処理とは
非同期処理は、クライアントサイドからサーバーサイドへ通信が発生する、ファイルを読み込むなど、何らかの通信が発生する処理で起こる。
- Web APIを叩く
- データベースへクエリを投げる など
非同期処理では、時間がかかる処理のせいで他の処理が止まらないように実行完了を待たずに次の処理に進む。この処理は、シングルスレッドの言語であるJavaScriptが効率よく処理を行うために大事な仕組みである。
長所と短所
長所 | 短所 |
---|---|
複数の処理を並行して効率よく実行できる | 制御が難しい |
重い処理や時間のかかる通信中にユーザーに別の操作を許せる | 処理が実行中なのか実行完了したのかわかりにくい |
対応
Promiseやasync / awaitを使うことで、非同期処理を場合によって同期的に制御する。
また、これらを使用する時は型を使うとより便利。
コールバック関数の失敗例
GitHubのREST APIを参考に、自分のGitHub IDを指定し、プロフィール情報を取得するコールバック関数を記述。
export default function callbackSample() {
// 自分のGitHubのIDを指定
const url = "https://api.github.com/users/milkandhoney995"
// コールバックで呼び出す非同期処理
const fetchProfile = () => {
return fetch(url)
.then((res) => { // 非同期処理の実行が終わったら実行する.then()
// レスポンスのBodyをJSONで読み取った結果を返す
res.json()
.then((json) => {
console.log("Asynchronous Callback sample 1:", json)
return json
})
.catch((error) => {
console.error(error)
})
})
.catch((error) => {
console.error(error)
})
}
const profile = fetchProfile()
console.log("Asynchronous Callback sample 2:", profile)
}
出力
先にAsynchronous Callback sample 1ではなく、2が先に来てしまっている。
> Asynchronous Callback sample 2: Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
> Asynchronous Callback sample 1:
{login: 'XXXXX', id: …}
また、Asynchronous Callback sample 2では、jsonではなくPromiseが返ってきてしまう、つまり
const profile = fetchProfile()
にjsonが入ってきていないことになる。
ここで、fetchProfile関数は非同期処理の中で実行されているため、fetchProfile関数も必然的に非同期処理になる。そのため、fetchProfile関数で正しい値を受け取るためには、同期的に待ってからprofileに代入する必要がある。
コールバック関数では、then()でつないでいった中でしかレスポンスのjsonが有効でない、という状態になっている。
そのため、Promiseやasync / awaitを使ってfetchProfileを書き換える必要がある。
Promise型
実行完了後の値を定義
非同期処理の実行結果は、
Promise<string> // 最終的にstring型を返す非同期処理
のように定義できる
Promiseの状態
状態 | |
---|---|
Promise<pending> | 初期状態 / 実行中 |
Promise<fullfilled> | 処理が成功して完了した状態 |
Promise<rejected> | 処理が失敗して完了した状態 |
Promiseを使用した方法
export default function promiseSample() {
const url = "https://api.github.com/users/milkandhoney995"
type Profile = {
login: string
id: number
}
type FetchProfile = () => Promise<Profile | null>
// 非同期処理を行い、最終的にProfileかnullを返す
const fetchProfile: FetchProfile = () => {
// 最終的にPromiseを返す
return new Promise((resolve, reject) => {
return fetch(url)
.then((res) => {
res.json()
.then((json) => {
console.log("Asynchronous Promise sample 1:", json)
// return せずに、resolveしてjsonを返す
resolve(json)
})
.catch((error) => {
console.error(error)
reject(null)
})
})
.catch((error) => {
console.error(error)
reject(null)
})
})
}
fetchProfile()
.then((profile: Profile | null) => {
if (profile) {
console.log("Asynchronous Promise sample 2:", profile)
}
})// Promiseを返すので、then(), catch()が使える
.catch(() => {
})
}
出力
想定した順番で出力された。
> Asynchronous Promise sample 1: {login: ... }
> Asynchronous Promise sample 2: {login: ... }
ただ、この状態でもコールバックが多く複雑。
なので、async / awaitでもっとみやすく書いていく。
Async / Await
// ここでasyncをつけると、中の関数でawaitが使える
export default async function asyncAwaitSample() {
const url = "https://api.github.com/users/milkandhoney995"
type Profile = {
login: string
id: number
}
type FetchProfile = () => Promise<Profile | null>
// async, awaitをつける
const fetchProfile: FetchProfile = async () => {
const response = await fetch(url)
.then((res) => res)
.catch((error) => {
console.error(error)
return null
})
if (!response) {
return null
}
const json = await response.json()
.then((json) => {
console.log("Asynchronous async/await sample 1:", json)
return json
})
.catch((error) => {
console.error(error)
return null
})
if (!json) {
return null
}
return json
}
const profile = await fetchProfile()
if (profile) {
console.log("Asynchronous async/await sample 2:", profile)
}
}
出力
> Asynchronous async/await sample 1: {login: ... }
> Asynchronous async/await sample 2: {login: ... }
Discussion