はじめに
Typescriptを書いてて、非同期処理にPromiseとasync/awaitを使う2種類の書き方があったのでまとめました。
まとめ
- 個人的にPromiseだけで書くよりもasync/awaitを使うほうがシンプルに書けると思う
非同期処理とは
- 通信が発生する処理で起きる
- Web APIを叩く
- データベースへクエリを投げる
- 実行完了を待たずに次の処理に進む
- Javascriptはシングルスレッドの言語
* 非同期APIにより効率よく処理を行うことが可能
非同期処理は一長一短
- 複数の処理を並行して効率よく実行できる
- 思い処理や時間のかかる通信中にユーザーに別の操作を許可するなど
- 制御が難しい
- 処理が実行中なのか実行完了したのかトレースしにくい
- どう対処すべきか
-
Promise
やasync/await
で非同期処理を同期的に制御する - 型をつけることでわかりやすく!
-
非同期処理をそのままにコードで書く
下記のコードの場合、Asynchronous Callback Sample 2が先に表示されてしまう。。
const url = 'https://api.github.com/users/xxxxxx'
// コールバックで呼び出す非同期関数(fetch)
const fetchProfileCallback = () => {
return fetch(url)// fetch():非同期処理となる
.then((res) => {
// レスポンスbodyをJSONとして読み取った結果を返す
res
.json()
.then((json: Profile) => {
console.log('Asynchronous Callback Sample 1:', json)
//Asynchronous Callback Sample 1: {login: ......}
return json
})
.catch((error) => {
console.error(error)
return null
})
})
.catch((error) => {
console.error(error)
return null
})
}
const profile = fetchProfileCallback()
// 非同期処理が完了していないのでPromise<pending>が表示される
console.log('Asynchronous Callback Sample 2:', profile)
//Asynchronous Callback Sample 2: Promise {<pending>}
Promise型で実行完了後の値を定義する
- 非同期処理の実行結果は
Promise<string>
のように定義する
Promiseの状態
-
Promise<pending>
: 初期状態/実行中 -
Promise<fulfilled>
: 処理が成功して完了した状態 -
Promise<rejected>
: 処理が失敗して完了した状態
Promiseで順番を制御する
Promiseを使うと、Asynchronous Promise Sample 1のあと、Asynchronous Promise Sample 2が順番通りに表示される
Promiseを使った例
const url = 'https://api.github.com/users/xxxx'
type Profile = {
login: string
id: number
}
type FetchProfile = () => Promise<Profile | null> //型を定義
const fetchProfilePromise: FetchProfile = () => {
return new Promise((resolve, reject) => {
fetch(url)
.then((res) => {
// レスポンスbodyをJSONとして読み取った結果を返す
res
.json()
.then((json: Profile) => {
console.log('Asynchronous Promise Sample 1:', json)
resolve(json) // Promiseを扱う場合、resolve()を使う
})
.catch((error) => {
console.error(error)
reject(null)
})
})
.catch((error) => {
console.error(error)
reject(null)
})
})
}
fetchProfilePromise().then((profile: Profile | null) => {
if (profile) {
console.log('Asynchronous Promise Sample 2:', profile)
}
})
async/awaitで制御する
export default async function asyncAwaitSample(): Promise<void> { //asyncをつけることができる
const url = 'https://api.github.com/users/xxxxx'
type Profile = {
login: string
id: number
}
type FetchProfile = () => Promise<Profile | null>
// async/awaitでコールバック関数を同期的な処理に置き換える
const fetchProfile: FetchProfile = async () => {
const response = await fetch(url)
.then((response) => response) //fetch()が成功していたらそのまま返す
.catch((error) => {
// 失敗していたら
console.error(error)
return null
})
// responseがnullならfetchに失敗している
if (!response) {
return null
}
const json = await response
.json()
.then((json: Profile) => {
console.log('Asynchronous Promise Sample 1:', json)
return json
})
.catch((error) => {
console.error(error)
return null
})
// jsonがnullならレスポンスBodyの読み取りに失敗している
if (!json) {
return null
}
return json
}
fetchProfile().then((profile: Profile | null) => {
if (profile) {
console.log('Asynchronous Promise Sample 2:', profile)
}
})
// さらに同期的な処理にする
const profile = await fetchProfile()
if (profile) {
console.log('Asynchronous Promise Sample 3:', profile)
}
}