Edited at

【jQuery => async/await】再帰で可変長の非同期処理の要求を順番に実行する


はじめに

js勉強中に表題の実現に困ったので備忘録にまとめます。

要求数が一定ではない、非同期処理を順序立てて処理する方法です。

他のやり方もあるかもしれませんが、今回の場合は再帰呼び出しが便利だと思います。

同機能の実現をjQueryの書き方とasync/awaitを用いた書き方の両方で試してみました。

どちらも結果は同じになります。


Promise

非同期処理を作成する胴元的存在。async/awaitを使っても元をたどればこれに行きつく。


promise.js

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バージョン


saiki.js

//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バージョン


saiki.js

//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を利用したやり方で気を付けたほうがいいと思うところは↓


saiki.js


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を両方つけている気持ちになる。

ですが、つけないときちんと動きませんでした。


saiki.js


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があるという形になっていますので問題ないようです。


saiki.js


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を利用していくのもいいと思います。