QA@IT

jQueryの非同期・同期処理について

13320 PV

jQueryを使用し下記のようなJavaScriptを書きました。
(エラー処理等は省いています)

console.log('start');
for (var i = 0; i < 3; i++) {
    $.ajax({
        url: 'http://example1.com/test1',
        type: 'get',
        async: false,
        success: function(data){
            console.log('a'+i);
            $.ajax({
                url: 'http://example.com/test2',
                type: 'post',
                async: false,
                data: obj,
                success: function(){
                    console.log('b'+i);
                },
            });
    },
    });
}
console.log('end');

実行する意図した通り、

start
a0
b0
a1
b1
a2
b2
end

と実行されるわけです。

しかし、(既にお気付きの方もいらっしゃるかと思いますが)asyncをfalseと設定し同期処理するように
設定しているため、ブラウザによっては短時間でも固まって(フリーズ)しまいます。

asyncに敢えてfalseと設定しているのは処理の順番が重要だからです。
上記のコードでは1個目のAjaxで取得したdataに対し特に処理を行っていませんが、実際にはdataから
取り出したデータを加工し、objを作ってAjaxでpostするという処理を行っています。
また、forループで使用している変数iもdata加工時に使用するため適切なiを使用したいと考えています。

そこで質問なのですが、上記の条件を満たした上で全てのブラウザで固まらなくなる方法というのは
どうすれば良いのでしょうか?どうぞ、ご存知の方、宜しくお願い致します。

予想するにjQueryのDeferredメソッド使えば出来そうだったので自分でやってみたのですが、forループの
iの値が全て非同期で最初に全て回ってa3 b3となってしまい上手く行きませんでした…。

使用しているjQueryのバージョンは1.10.1になります。
(Deferredの動きがバージョンによって多少違うらしいですね)

回答

単に i の値をコールバック内で使うだけなら次のように for 内にスコープを作って var j = i; とかで良いと思います。

console.log('start');
for (var i = 0; i < 3; i++) {
    (function(){
        var j = i;
        $.ajax({
            url: 'http://example1.com/test1',
            type: 'get',
            async: true,
            success: function(data){
                console.log('a'+j);
                $.ajax({
                    url: 'http://example.com/test2',
                    type: 'post',
                    async: true,
                    data: data,// obj?
                    success: function(){
                        console.log('b'+j);
                        if (j == 2)
                        {
                            console.log('end');
                        }
                    }
                });
            }
        });
    })();
};

ただ、この方法だと同時に3リクエスト送られるので、 0 => 1 => 2 と直列にリクエストを送るなら次のような感じでしょうか。

console.log('start');
var success = function(){
    console.log('end');
};
for (var i = 2; i >= 0; i--) {
    (function(){
        var j = i;
        var s = success;
        success = function(){
            $.ajax({
                url: 'http://example1.com/test1',
                type: 'get',
                async: true,
                success: function(data){
                    console.log('a'+j);
                    $.ajax({
                        url: 'http://example.com/test2',
                        type: 'post',
                        async: true,
                        data: data,// obj?
                        success: function(){
                            console.log('b'+j);
                            s();
                        }
                    });
                }
            });
        }
    })();
}
success();

Deferred の方が直感的に書けます。

console.log('start');
var defer = $.Deferred().resolve();
for (var i = 0; i < 3; i++) {
    (function(){
        var j = i;
        defer = defer
            .then(function(){
                return $.ajax({
                    url: 'http://example1.com/test1',
                    type: 'get',
                    async: true
                })
            })
            .then(function (data) {
                console.log('a'+j);
                return $.ajax({
                    url: 'http://example.com/test2',
                    type: 'post',
                    async: true,
                    data: data,// obj?
                });
            })
            .then(function (data) {
                console.log('b'+j);
            })
        ;
    })();
}
defer.then(function(){
    console.log('end');
});
編集 履歴 (0)
  • 早速の回答有難う御座います。おお、滅茶苦茶Goodです!並列ではなく直列かつDeferredの最高です。求めていたのドストライクです! -

Deferred を使う版、then と done を使い分けたほうが意図が明確で良いかも。

console.log('start');
var defer = $.Deferred().resolve();
for (var i = 0; i < 3; i++) {
    (function(){
        var j = i;
        defer = defer
            .then(function(){
                return $.ajax({
                    url: 'http://localhost/?' + j,
                    type: 'get',
                    async: true
                })
            })
            .done(function(data){
                console.log('a'+j);
            })
            .then(function(data){
                return $.ajax({
                    url: 'http://localhost/?' + j,
                    type: 'post',
                    async: true,
                    data: data
                });
            })
            .done(function(data){
                console.log('b'+j);
            })
        ;
    })();
}
defer.done(function(){
    console.log('end');
});
編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。