async/awaitやPromiseで気をつけること
ES2017のasync/awaitのキソ練習 - あーありがち(2018-05-26) のあとにがっつりハマっていたのでそこで得た知見をメモ。
asyncの効果は各functionブロックで切れるので逐一指定が必要
例えば
async foo() {
return new Promise((resolve, reject) => {
..
})
}
ってコードがあるじゃろ? この Promise の中では await できんのじゃ。正解はこうじゃ。
async foo() {
return new Promise(async (resolve, reject) => {
..
})
}
Promiseではちゃんとrejectする、rejectしてすぐreturnする
async foo() {
return new Promise(async (resolve, reject) => {
const bar = await this.bar()
if ( !bar ) {
return reject(new Error('message'))
}
resolve(bar)
})
}
async function は Promise を返すので、通常の function のように値を返すことはできない。成功も返せないし失敗も返せない。
失敗は必ず reject() で教える必要がある。
そしてもう一つ、reject() は単なる function call であり、それが終わったら次に処理が進んでしまう。ちゃんと return して foo() を抜けなければいけない。
resolve ばかりを考えていると UnhandledPromiseRejectionWarning の警告が出まくることになる。ちゃんと reject してあげよう、そしてすぐ抜けよう。
async/awaitで待つコードを閉じ込める
async foo() {
return new Promise(async (resolve, reject) => {
let c = {}
this.bar().forEach((e) => {
c[e] = await this.baz(e)
})
})
}
みたいな、同期処理の中に非同期処理が紛れ込んでいてさらに全体のブロックの中で副作用ベースで変数を組み立てる、みたいなやつは大変あぶない。
async foo() {
return new Promise(async (resolve, reject) => {
// 非同期処理のカタマリ
// 同期処理のカタマリ
})
}
に分離しよう。
Promise.allで全部待て
上のコードはもっと言うとループの1回目は待つかもしれないけど全部は待ってくれないコードになり得る。そこで実際にやるとしたら Promise.all() を使って以下のような感じになる。
async foo() {
return new Promise(async (resolve, reject) => {
// 非同期処理のカタマリ
const result = await Promise.all(this.bar().map(async (e) => {
return ..
})
// 同期処理のカタマリ
result.forEach((e) => {
..
})
..
})
}
ここに上で述べた resolve, reject を加えると以下のような感じがいったん完成形と言えそう。
async foo() {
return new Promise(async (resolve, reject) => {
// 非同期処理のカタマリ
const result = await Promise.all(this.bar().map(async (e) => {
return .. await this.baz() ..
})
// 同期処理のカタマリ
let c = {}
result.forEach((e) => {
const [key, value] = e
.. // 同期処理だけなら副作用ベースでも従来通り大丈夫
if ( ) {
return reject(new Error())
}
})
resolve(c)
})
}