はじめに
async/awaitで勘違いしていたことがありました。
それは、async function 内で 非同期処理を await で待機した場合、そのasync function 自身が同期的に振舞うようになるものだと思っていたことです。
基本的な事とは思いますが、最近同じような問題に直面したので記事にまとめる事にしました。
async/awaitの基礎
まずは、以下のような非同期処理を含む関数があるとします。
function funcA(){
console.log("a1");
new Promise((resolve)=>{
setTimeout(
()=>{
console.log("a2");
resolve();
}, 1000
);
});
console.log("a3");
}
この関数を実行すると、出力結果は以下のようになります。
a1
a3
a2
これを
a1
a2
a3
のように順番通りに出力したいとします。
awaitを使って実現すると以下のようになります。
async function funcA_await(){
console.log("a1");
await new Promise((resolve)=>{
setTimeout(
()=>{
console.log("a2");
resolve();
}, 1000
);
});
console.log("a3");
}
これでfuncA_await()が同期的に振舞うようになりました。
正確に表現するならば、非同期関数であるfuncA_await()内の非同期処理をawaitによって待機し同期的に処理していると言えばよいでしょうか。
これが基本的な async/await の使い方です。
このぐらい単純な場合には、難し事ではありません。
非同期関数と同期関数を順番に呼び出す。
さて問題はここからです。
先ほどのfuncA_await()の終了後に新たな処理を行い、出力データとして以下が欲しいとします。
a1
a2
a3
b1
b2
b3
単純に以下のように実装して実行してみます。
function funcB(){
console.log("b1");
console.log("b2");
console.log("b3");
}
function funcC(){
funcA_await();
funcB();
}
この実装では、出力は残念ながら以下のようになります。
a1
b1
b2
b3
a2
a3
非同期関数であるfuncA_await()の終了を待たずにfuncB()の処理が始まっています。
これをasync/awaitを使って当初の目論見通りに動かすようにするには以下のようにします。
async function funcC_await(){
await funcA_await();
funcB();
}
しっかりとasync/await、Promiseを勉強していれば当たり前の話だとは思いますが、非同期関数であるfuncC_await()の実行を待つには、await()やPromise.then()を使う必要があります。
蛇足ですがこのfuncC_await()の後に処理を続ける場合は、同じようにasync/awaitを使う必要があります。
function funcE(){
console.log("e1");
console.log("e2");
console.log("e3");
}
async function funcD(){
await funcC_await();
funcE();
}
このような実装を続けると非同期処理が1つしかないにもかかわらず、いつの間にかにasync/await地獄に陥っていたりします。
まとめ
async/awaitは、
- 非同期処理を同期的に扱う仕組み
- それらを用いたasync functionはあくまでも非同期処理
という事です。
このことから非同期処理は、しっかりと設計をして扱わなければいけない事が分かるかと思います。
この記事の場合は、
- 初めの非同期処理を同期処理に置き換える
- その後の一連の処理を順番に依存しないように作り替える
など根本的な対策が必要なのだと思います。
もっとも、根本の原因を取り除ける場面は多くなく小手先の対応を余儀なくされる場面の方が多いかと思いますが。
他にもっと良い方法があったら教えてもらいたいものです。
最後に
async/awaitも万能ではありません。
適切に使えるようになりたいものです。