LoginSignup
4
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-09-11

はじめに

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

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5