はじめに
js勉強中に表題の実現に困ったので備忘録にまとめます。
要求数が一定ではない、非同期処理を順序立てて処理する方法です。
他のやり方もあるかもしれませんが、今回の場合は再帰呼び出しが便利だと思います。
同機能の実現をjQueryの書き方とasync/await
を用いた書き方の両方で試してみました。
どちらも結果は同じになります。
Promise
非同期処理を作成する胴元的存在。async/awaitを使っても元をたどればこれに行きつく。
var sample = new Promise(function(resolve) {
resolve('true!');
});
↑のように記述すればsampleは非同期処理として扱われます。次のasyncと性質は似ていてこれを簡略化したような考え方ですね。
async
これを定義すると非同期関数となりPromiseを返します。async function(){}
という形になります。
jQueryでいうところの$.ajax()などと同じように.then()でつなげられるようになります。
async関数の返り値
return
返り値のPromiseはresolveとなる。
throwや例外
返り値のPromiseはrejectとなる。
await
async関数の進みは、これをつけた処理の部分でいったんPromiseの結果が返されるまで待ちます。なのでなかでPromiseを使う。
コード
jQueryバージョン
//jQuery $.Deferred()
var asyncTest=function(value){
var def=$.Deferred();
setTimeout(() => {
def.resolve(value * 5);
}, value * 200);
return def.promise();
};
var order=[1,3,5,7,9];//テスト用の処理要求
var TestFunc=function(def){
var req=order.shift();
$.when(asyncTest(req))
.then(
function(data){
console.log(data);
if(order.length){
TestFunc(def);
}
else{
def.resolve('complete!!');
}
},
function(jqXHR,textStatus,errorThrown){
alert(textStatus+"_"+errorThrown);
def.reject();
}
);
return def.promise();
};
var def=$.Deferred();
TestFunc(def)
.then((data) => {
console.log(data);
});
async/awaitバージョン
//JavaScript async/await
const _asyncTest=(value) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(value * 5);
}, value * 200);
});
};
const _order=[1,3,5,7,9];//テスト用の処理要求
const _TestFunc = async () => {
var req=_order.shift();
var data = await _asyncTest(req);
console.log(data);
if(_order.length){
return await _TestFunc();
}
else{
return 'complete!';
}
};
_TestFunc()
.then((data) => {
console.log(data);
});
jQueryの方は特別なことはしていません。比較してみると、多少はasync/awaitの方が記述量が少なくなっています。これは大きな利点ですね。
ちなみにasync/awaitを利用したやり方で気を付けたほうがいいと思うところは↓
const _TestFunc = async () => {
var req=_order.shift();
var data = await _asyncTest(req);
console.log(data);
if(_order.length){
return await _TestFunc();//<=再帰呼び出しの際にawaitをつける
}
else{
return 'complete!';
}
};
一見すると、_TestFunc()はasyncの関数だから、呼び出し時にawaitをつけなくてもいいような…と思いました。awaitはasyncの中に書くものだからawaitをつけると_TestFunc()にはasyncとawaitを両方つけている気持ちになる。
ですが、つけないときちんと動きませんでした。
const _TestFunc = async () => {
var req=_order.shift();
var data = await _asyncTest(req);//<=Promiseを返す
console.log(data);
if(_order.length){
return await _TestFunc();
}
else{
return 'complete!';
}
};
↑この_TestFunc()の性質を考えたところ、中でasyncTest()を呼び出していて、これはPromiseを返します。awaitはPromiseを返すものにつけられるので、結果的にPromiseを返すものを内包しているので_TestFunc()にawaitをつけたら想定の動きとなります。
さらにasyncの中で処理終了まで待ちたい関数を呼び出すのだから、再帰関数も例外ではなくawaitはつけないといけないという当たり前の結論に行きつきました。コードの形を見るにasyncの中にawaitがあるという形になっていますので問題ないようです。
const _TestFunc = async () => {
var req=_order.shift();
var data = await _asyncTest(req);
console.log(data);
if(_order.length){
return await _TestFunc();//<=呼び出しの再帰元まで戻るためにreturnは忘れずに
}
else{
return 'complete!';//<=同じくreturn
}
};
もちろんreturnもつけます。これがないと再帰元に結果が返っていきません。_TestFuncにつないだ.then()から値を取り出したいときは忘れないようにします。
おわりに
jQueryのやり方
メリット:慣れ親しんだやり方
デメリット:将来的に古くなる
async/awaitのやり方
メリット:コードがすっきりする
デメリット:バベルしないといけない
未来志向で行きたいと思うならasync/await
を利用していくのもいいと思います。