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: setTimeoutresolveがないので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: setTimeoutsetTimeoutは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)
}
// 結果
// undefinedundefinedが帰るがこれは仕様のようだ。
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の中に書けば良い。
例を挙げ始めたらきりがなくまだ色々なケースがありそうだ。
実は私も「あれ?なんで動かない?」となる場面がしばしばあるため記事にまとめた。
よく見れば分かるもののコードが長くなればより発生しやすい。
読みやすいコードも意識して書いていきたいところである。
