Proimse
とasync/await
について再確認する通常これらは非同期処理が終了したかどうかを判断するために利用される。
しかし書き方が少し複雑で構文を少しでも間違えていてもエラーが出ること無く意図しない動作(非同期処理を待たず次にいってしまう)をする事が頻繁にあるため整理しておく。
Promise
およびasync/await
はこれまでに使ったことがある人向け。
基本的な説明はしないので注意。
この記事では**「ぱっと見で正しい動作をしそうだが、なぜか動作しない」**例をいくつか示す。
Promise
まず最初のコードは次の数字順で出力される想定で組んでいる。
※5: error
はエラー処理のため表示されない想定
console.log("1: Start")
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("3: setTimeout")
resolve()
}, 5000)
}).then(
console.log("4: then")
).catch(
console.log("5: error")
)
console.log("2: End")
だが、実際には次の実行結果となる。
1: Start
4: then
5: error
2: End
3: setTimeout
これは実行時エラーもなく正常に終了するが結果がおかしい。then
とerror
が両方出ておりsetTimeout
は一番最後にきている(Promise
が無視されている)
then
およびcatch
のコードがアローファンクションになっていないため。
次に直せば正しく動作する
console.log("1: Start")
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("3: setTimeout")
resolve()
}, 5000)
}).then(() => {
console.log("4: then")
}).catch(() => {
console.log("5: error")
})
console.log("2: End")
実行結果
1: Start
2: End
3: setTimeout
4: then
もう一度言うがEnd
が先に出るのは正常である。
resolve
がないPromise
さてresolve
を記述し忘れるとどんな動作になるだろうか。
先程の正しい実行結果を得られたコードからresolve
を消去してみる。
※ここからは分かりやすいように各項目の数字を上から順に変更した。
console.log("1: Start")
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("2: setTimeout")
//ここのresolve()を消去した
}, 5000)
}).then(()=>{
console.log("3: then")
}).catch(()=>{
console.log("4: error")
})
console.log("5: End")
実行結果
1: Start
5: End
2: setTimeout
resolve
がないのでthen
が実行されなかった。
同様に処理の失敗をハンドリングする場合はreject
も正しく書かねばならない。
一応書いておくとPromise
は通常then
とreject
をアローファンクションで結ぶ必要があるり面倒なのでPromise
の前にawait
を置いてやるだけで同期的に処理を待つ事ができるようにするコードだ。
...と言われて次のコードを書くが処理を待たず次に行ってしまう。
※async
はfunction
に宣言する必要があるのでコードは関数となっている。
const awaitFunc = async () => {
console.log("1: Start")
await new Promise((resolve, reject) => {
setTimeout(() => {
console.log("2: setTimeout")
}, 2000)
resolve()
})
console.log("3: End")
}
このコードも1,2,3と順番に出ることを想定している。
しかし、実行結果は次のように処理を待たずEnd
が出てしまう。
1: Start
3: End
2: setTimeout
文法も正しくエラー表示なしにもかかわらずawait
が機能していないように見える。なぜだろうか。
これはresolve
の位置が悪い。
よくコードを見るとresolve
は正しくpromise
内にあるが、setTimeout
が非同期で処理を開始した直後に次のresolve
コードが実行されるので先に3: end
が表示される
これはawait
関係なくPromise
のみの場合でも発生するので注意。
次のコードは3: End
が表示されない。
const awaitFunc = async () => {
console.log("1: Start")
await new Promise((resolve, reject) => {
setTimeout(() => {
console.log("2: setTimeout")
}, 2000)
})
console.log("3: End")
}
// 結果
// 1: Start
// 2: setTimeout
これは前にも述べたがresolve
を書き忘れているケース。
await
はresolve
が発生するまで処理をブロックするのでそれ以降のコードは実行されないままだ。
await
を書くとエラーになるケースconst awaitFunc = () => {
console.log("1: Start")
await new Promise((resolve, reject) => {
setTimeout(() => {
console.log("2: setTimeout")
resolve()
}, 2000)
})
console.log("3: End")
}
これはすぐ分かるかもしれないがawaitFunc = ()
の箇所にasync
が無い。
es6構文となっており次のように書く人も多いかと思うので両方記載しておく。
async function awaitFunc() { ... //これでOK
const awaitFunc = async () => { ... //es6はこれ
↑は厳密には違う2つになるらしい。詳しくは私もわからない。
次のエラーが出る場合。
SyntaxError: Unexpected reserved word
コード
const awaitFunc = async () => {
console.log("1: Start")
new Promise((resolve, reject) => {
await setTimeout(() => {
console.log("2: setTimeout")
resolve()
}, 2000)
})
console.log("3: End")
}
これはあまり難しくない。await
がPromise
の中に入ってしまっている。
コードが長くなってくるとasync function
の中にawait
を正しく入れているのになぜ動作しないの?となるケースはよくある。
Promise
ではないものをawait
しているケースこれもよくありそうだ。何度も言うがPromise
をawait
する必要がある。
const awaitFunc = async () => {
console.log("1: Start")
await setTimeout(() => {
console.log("2: setTimeout")
}, 2000)
console.log("3: End")
}
実行結果
1: Start
3: End
2: setTimeout
setTimeout
はpromise
ではないのでエラーなしで3: End
が先に実行されてしまう。
※ちなみにsetTimeout
はTimeout
という名前のオブジェクトがreturn
される。
await
を入れるとPromise
はReturn
されないconst awaitFunc = async () => {
const test = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 2000)
})
console.log(test)
}
// 結果
// undefined
undefined
が帰るがこれは仕様のようだ。
await
を外すと Promise { <pending> }
が帰ってくる。
正しく動作するawait
のPromsie
にthen
をつけたらどうなるか。
const awaitFunc = async () => {
console.log("1: Start")
await new Promise((resolve, reject) => {
setTimeout(() => {
console.log("2: setTimeout")
resolve()
}, 2000)
}).then(() => {
console.log("4: then")
})
console.log("3: End")
}
実行結果
1: Start
2: setTimeout
4: then
3: End
先に4: then
、その後に3: End
が処理される。
Promise.all
もawait
できるconst awaitFunc = async () => {
console.log("1: Start")
const test = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("2: setTimeout")
resolve()
}, 2000)
})
Promise.all([test]).then(()=>{
console.log("3: Promise.all")
})
console.log("4: End")
}
少し複雑だが、このコードは4: End
が先に出てしまう。
1: Start
4: End
2: setTimeout
3: Promise.all
次のコードのようにPromise.all
をawait
することも可能だ。
Promise.all
もPromise
オブジェクトをリターンするから、といえば納得が行く。
const awaitFunc = async () => {
console.log("1: Start")
const test = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("2: setTimeout")
resolve()
}, 2000)
})
await Promise.all([test]).then(()=>{ //Promise.allにはPromiseの「配列」を渡すこと!
console.log("3: Promise.all")
})
console.log("4: End")
}
実行結果
1: Start
2: setTimeout
3: Promise.all
4: End
※このコードは良くない。なぜならこの処理なら4: End
はPromise.all
の中に書けば良い。
例を挙げ始めたらきりがなくまだ色々なケースがありそうだ。
実は私も「あれ?なんで動かない?」となる場面がしばしばあるため記事にまとめた。
よく見れば分かるもののコードが長くなればより発生しやすい。
読みやすいコードも意識して書いていきたいところである。