【JavaScript】Promiseによる非同期処理
非同期処理
非同期処理とは
非同期処理の前に、同期処理について簡単に説明しておきます。 まずは以下の例を見てください。
const print1 = () => {
console.log('start')
console.log('Hello World!')
console.log('end')
}
start
Hello World!
end
同期処理は、上記のようにコードの順に処理を実行することです。 そのため、1 つ 1 つの処理の終了を待たなければいけません。
では次の例はどうでしょう。
const print2() = () => {
console.log('start')
setTimeout(() => console.log('Hello World!'), 3000)
console.log('end')
}
setTimeout()
は、引数に一定時間後に実行したい処理を指定します。
この例の場合、3 秒後に Hello World!と表示するという処理になります。
これを実行すると、最初の例とは違う結果になります。
start
end
Hello World!
この処理のイメージは以下のようになります。
タイミング | print2() | setTimeout() |
---|---|---|
1 | 処理開始 | |
2 | console.log('start') | |
3 | setTimeout() | 処理開始 |
4 | console.log('end') | 3 秒待機 |
5 | console.log('Hello World') |
console.log('end')
は、setTimeout()
の結果を待たずして実行されています。
この間、2 つの処理は同時に実行されます。
このような処理の実行方法を非同期処理といいます。
なぜ非同期処理が必要かというと、同期処理の場合は処理の関連性に関係なく、1 つ 1 つ順に終わらせなければいけないため時間がかかってしまいます。 処理に関連性がないなら別々で実行したほうが早いのは明白です。 2 つのりんごを片手で順に取るより両手で 2 つ取ったほうが早いよね、ということです。
コールバック関数
コールバック関数とは、関数内から実行される関数のことになります。
以下の例の場合、funcB
はfuncA
で実行されているためコールバック関数となります。
const funcA = (f) => {
console.log('Do funcA')
f()
}
const funcB = () => {
console.log('Do funcB')
}
funcA(funcB)
非同期の処理には、このコールバック関数が用いられます。
先程のsetTimeout
に指定した処理もコールバック関数になります。
Promise
Promise とは
Promise
とは、非同期処理が完了(成功または失敗)した後の処理を定義するためのものです。
Promise
の代表的な例として、Fetch APIがあります。
これは API などにリクエストを送信し、データを取得するためのものです。
fetch
は戻り値としてPromise
を返します。
fetch('https://hoge.com/fuga')
.then((response) => {
//成功時の処理
})
.catch((error) => {
//エラー時の処理
})
.finally(() => {
//最後に必ず実行する処理
})
Promise
には、待機、成功、失敗の 3 つの状態があります。
上例では、https://hoge.com/fuga
へリクエストを送信し、レスポンスが返ってくるまでが待機状態、
正常レスポンスが返ると成功状態、エラーレスポンスが返ると失敗状態となります。
成功時の処理はthen
のコールバック関数として指定し、失敗時の処理はcatch
のコールバック関数として指定します。
then
のコールバックの引数には処理の結果、fetch
の場合はレスポンス情報になります。
catch
のコールバックの引数はエラー情報となります。
また、成功と失敗に関係なく、最後に実行したい処理としてfinally
を指定することもできます。
Promise の定義
Promise
を使用することで、簡単に非同期処理を作ることができます。
以下の例を見て下さい。
asyncFunc = (arg) => {
return new Promise((resolve, reject) => {
if (arg === 0) {
resolve('success')
} else {
reject('failed')
}
})
}
asyncFunc(0) //successと表示される
.then(result => console.log(result))
.catch(error => console.log(error))
asyncFunc(1) //failedと表示される
.then(result => console.log(result))
.catch(error => console.log(error))
非同期処理の関数を作成する場合、戻り値としてPromise
を返すようにします。
Promise
は、new
(コンストラクタ)を使ってインスタンスを生成しますが、その引数に非同期で実行したい処理を指定します。
ここ重要なのが、引数のresolve
とreject
です。
resolve
は処理の成功を意味し、これを指定することでthen
へと繋がります。
引数に指定した値(オブジェクト)は、then
の引数に設定されます。
reject
は処理の失敗を意味し、これを指定することでcatch
へと繋がります。
引数に指定した値(オブジェクト)は、catch
の引数に設定されます。
Promise チェーン
非同期処理asyncFunc1()
の結果を受けて、非同期処理asyncFunc2()
を実行するにはどうすればよいでしょうか。
最初に思いつくのは、以下のようなネストによる定義方法です。
asyncFunc1()
.then((result1) => {
console.log('success1')
asyncFunc2()
.then((result2) => {
console.log('success2')
})
.catch((error2) => {})
})
.catch((error1) => {})
しかし、これでは処理が増えるとともにネストが増えてしまい、コードの可読性を損ないます。
これを解決するために、Promise チェーンというものがあります。
これは、戻り値にPromise
を指定することで、次のPromise
の処理を実行するというものです。
上記の例を Promise チェーンで定義すると以下のようになります。
asyncFunc1()
.then((result) => {
console.log('success1')
return asyncFunc2()
})
.then((result) => {
console.log('success2')
})
.catch((error) => {})
Promise チェーンの場合、catch
は 1 つで済みます。
ただし、error
にはそれぞれのエラー情報が設定されることに注意してください。
並列処理
Promise
の処理は、Promise.all()
を使用することによって並列に実行することができます。
並列に実行する処理を配列で定義し、その結果は配列で返されます。
const promises = [asyncFunc1(), asyncFunc2(), asyncFunc3()]
Promise.all(promises)
.then((results) => {
console.log(results[0]) //asyncFunc1()の結果
console.log(results[1]) //asyncFunc2()の結果
console.log(results[2]) //asyncFunc3()の結果
})
.catch((error) => {})
1 つでもエラーが発生した場合は、全体のエラーとしてcatch
が実行されます。
catch
の引数には、一番最初にエラーとなった処理の情報が設定されます。
async/await
async/await とは
async/await
は、Promise
による同期処理をより簡潔に定義できることを目的にした仕組みです。
詳細は後述しますが、Promise
のthen
が不要になるため、通常のプログラム同様に同期処理が定義できるようになります。
また非同期処理自体の定義も簡潔にできるようになります。
async 関数
関数にasync
を付与することで、Promise
を返す非同期処理にすることができます。
Promise
の定義で例に出したasyncFunc
をasync
を用いて定義すると以下のようになります。
const asyncFunc = async (arg) => {
if (arg === 0) {
return 'success'
} else {
throw 'failed'
}
}
return
がresolve
に該当し、throw
がreject
に該当します。
Promise
の定義よりもスッキリとしたのがわかります。
await
await
は非同期の処理に付与することで、その非同期処理を同期的にすることができます。
以下の例を見てください。
const doProcess = async () => {
console.log('start')
const result = await asyncFunc(0).catch((err) => {
console.log(err)
})
console.log(result)
console.log('end')
}
start
success
end
await
を付与することで、asyncFunc()
の処理が終了を待ってから、console.log('end')
が実行されていることがわかります。
つまり、await
より後に書かれた処理は、Promise
のthen
で定義された処理とイコールになります。
またawait
を付与すると、戻り値にはthen
の引数に設定されていた値が返されます。
await
は、async
関数内でのみ使用できるので注意が必要です。