Цепочка ajax и выполните ее последовательно. Jquery Deferred

У меня есть 3 процесса, для которых требуется выполнить ajax. Но он асинхронен и не выполняет то, что я хотел сделать.

Давайте скажем:

function a(param1, param2) {
     $.post(..., function(result){
         if(result){
            b();
         } else {
            console.log("failed a");
         }
     })
}

function b() {
      $.post(..., function(result){
         if(result){
            c();
         } else {
            console.log("failed b");
         }
     })
}

function c() {
     $.post(..., function(result){
         if(result){
            console.log("successful");
         } else {
            console.log("failed b");
         }
     })
}

Я хочу, чтобы он выполнялся следующим образом

a
b
c

Этот код будет работать отлично, как вы можете видеть. но если использовать цикл.

 var data = [{param1 : 1235, param2: 3214},  {param1 : 5432, param2: 9876}];

 $.each(data, function(k,v){
      a(v.param1, v.param2)
 });

Он не будет работать так, как ожидалось, и просто сделает:

a
a
b
b
c
c

вместо

a
b
c
a
b
c

Ответ 1

Есть много способов написать такое.

Гибкий подход - это отдельные "действия" из "последовательности", позволяющие:

  • выполняет функции a, b, c, чтобы инициировать асинхронное (ajax) действие, не зная, как они должны быть упорядочены.
  • a, b, c может быть повторно использован как часть одной или нескольких последовательностей или по отдельности, если требуется.

Вот способ кодирования этого подхода, используя .then() исключительно для логики цепочки:

function a() {
    return $.post(...).then(function(result) {
        if(result)
            return result;//continue on "success" path.
        else
            return $.Deferred().reject('a').promise();//convert success to failure.
    }, function() {
        return 'a';//continue on failure path.
    });
}
function b() {
    return $.post(...).then(function(result) {
        if(result)
            return result;//continue on "success" path.
        else
            return $.Deferred().reject('b').promise();//convert success to failure.
    }, function() {
        return 'b';//continue on failure path.
    });
}
function c() {
    return $.post(...).then(function(result) {
        if(result)
            return result;//continue on "success" path.
        else
            return $.Deferred().reject('c').promise();//convert success to failure.
    }, function() {
        return 'c';//continue on failure path.
    });
}

a().then(b).then(c).then(function() {
    console.log("successful");
}, function(id) {
    console.log("failed: " + id);
});

В качестве альтернативы, если вы хотите иметь одну асинхронную функцию, a, вызываемую из цикла, код может быть примерно таким:

function a(obj) {
    return $.post(...).then(function(result) {
        if(result)
            return result;//continue on "success" path.
        else
            return $.Deferred().reject(obj.id).promise();//convert success to failure.
    }, function() {
        return obj.id;//continue on failure path.
    });
}

var data = [{id:'A', param1:1235, param2:3214},  {id:'B', param1:5432, param2:9876}];
//Note how IDs are included so these data objects can be identified later in failure cases.

var dfrd = $.Deferred();//starter Deferred for later resolution.
var p = dfrd.promise();//A promise derived from the starter Deferred, forming the basis of a .then() chain.

//Build a .then() chain by assignment
$.each(data, function(i, obj) {
    p = p.then( function() {
        return a(obj);
    });//By not including a fail handler here, failures will pass straight through to be handled by the terminal .then() fail handler.
});

//Chain a terminal .then(), with success and fail handlers.
p.then(function() {
    console.log("successful");
}, function(id) {
    console.log("failed: " + id);
});

dfrd.resolve();//Resolve the starter Deferred to get things started.

Ответ 2

Вы можете подключать асинхронные вызовы, такие как вызовы ajax, используя отложенный объект jQuery и использовать "then".

Вы также можете изменить его, чтобы использовать функции, возвращающие объект отложенного обещания, вместо вызова ajax, как у меня в моем примере.

http://jsfiddle.net/q4cFv/

(Пример с функцией async: http://jsfiddle.net/q4cFv/1/)

$(function() {
    var delay = 3,
        span = $('span'),
        posts = [
            {
                input1: 'My name 1',
                input2: 'My address 1',
                input3: 'My country 1'
            },
            {
                input1: 'My name 2',
                input2: 'My address 2',
                input3: 'My country 2'
            },
            {
                input1: 'My name 3',
                input2: 'My address 3',
                input3: 'My country 3'
            },
            {
                input1: 'My name 4',
                input2: 'My address 4',
                input3: 'My country 4'
            }
        ],
        looper = $.Deferred().resolve();

    $.each(posts, function(i, data) {
        looper = looper.then(function() {
            return $.ajax({
                data: {
                    json: JSON.stringify(data),
                    delay: delay
                },
                method: 'post',
                url: '/echo/json/',
                dataType: 'json'
            }).done(function(response) {
                span.append('Response:<br />');
                for(key in response) {
                    span.append(key + ': ' + response[key] + '<br />');
                }
                $('span').append('Waiting ' + delay + ' seconds<br /><br />');
            });
        });
    });
});

Ответ 3

Я вижу, что c не зависит от результата b, а b не зависит от a > результат.

Следуя принципам GRASP (http://en.wikipedia.org/wiki/GRASP_(object-oriented_design), a не должен знать b и b не должны знать c.

Когда мы программируем, важно помнить о принципах или руководящих принципах GRASP.

Высокая сцепляемость и низкое сцепление означают, что наш код будет лучше, более многоразовым и легче поддерживать.

Основная функция, которая знает a, b и c, должна строить цепочные вызовы.

Функции:

    function a(param1, param2) {

        var deferred = $.Deferred();

        console.log("    function a: begin. Params " + param1 + " and " + param2);

        mockPost("a_url").done(function() {
            console.log("    function a: end ok. Params " + param1 + " and " + param2);
            deferred.resolve();
        }).fail(function() {
            console.log("    function a: end fail. Params " + param1 + " and " + param2);
            deferred.reject();
        });

        return deferred.promise();
    }

    function b() {

        var deferred = $.Deferred();

        console.log("    function b: begin");

        mockPost("b_url").done(function() {
            console.log("    function b: end ok.");
            deferred.resolve();
        }).fail(function() {
            console.log("    function b: end fail.");
            deferred.reject();
        });

        return deferred.promise();
    }

    function c() {

        // We suppose that c function calls to post function and anything more
        return mockPost("c_url");
    }

Основная функция:

    // Array with params for a function (a function is the first link in chain)
    var data = [{param1 : 1235, param2: 3214},  {param1 : 5432, param2: 9876}];

    // Array with calls to each fixed sequence a, b, and c. We iterate over data array
    var arrayFunctions = [];
    $.each(data, function(i,obj) {
        arrayFunctions.push(
            function() {
                console.log("Params in data with index " + i + ":");

                // We define the fixed sequence: a with params, b without params and c without params 
                return $.iterativeWhen( 
                    function() {
                        return a(obj.param1, obj.param2);
                    },
                    b,
                    c
                );
            }
        )
    });

    // Start the global process
    $.iterativeWhen.apply($, arrayFunctions)
    .done(function() {
        console.log ("----------------");
        console.log ("> Global Success");       
    })
    .fail(function() {
        console.log ("--------------");
        console.log ("> Global Fail");
    });

$. iterativeWhen не существует в jQuery, поэтому я его создал. Он работает с jQuery 1.8 и более поздними версиями.

$.iterativeWhen = function () {

    var deferred = $.Deferred();
    var promise = deferred.promise();

    $.each(arguments, function(i, obj) {

        promise = promise.then(function() {
            return obj();
        });
    });

    deferred.resolve();

    return promise;
};

Функция mockPost имитирует вызов $.post с вероятностью успеха:

function mockPost(url) {

        var deferred = $.Deferred();

        setTimeout(function() {
            if (Math.random() <= 0.9) {
                console.log("        request url: " + url +  "... ok");
                deferred.resolve();
            } else {
                console.log("        request url: " + url +  "... fail");
                deferred.reject();
            }
        }, 1000);

        return deferred.promise();
    }

Выход журнала:

Params in data with index 0: 
    function a: begin. Params 1235 and 3214 
        request url: a_url... ok 
    function a: end ok. Params 1235 and 3214 
    function b: begin 
        request url: b_url... ok 
    function b: end ok. 
        request url: c_url... ok 
Params in data with index 1: 
    function a: begin. Params 5432 and 9876
        request url: a_url... ok
    function a: end ok. Params 5432 and 9876 
    function b: begin 
        request url: b_url... ok 
    function b: end ok. 
        request url: c_url... ok 
---------------- 
> Global Success 

jsFiddle здесь: http://jsfiddle.net/E2tp3/

Ответ 4

Ваша проблема в том, что вы вызываете все a сразу, но вы хотите дождаться первого цикла, прежде чем переходить к следующему. Вы хотите дождаться завершения предыдущего цикла a, прежде чем вы начнете следующий цикл.

Предположим, что a, b, c принимают обратный вызов и передают его,

a будет выглядеть как

function a(param1, param2,callback) {
     $.post(..., function(result){
         if(result){
            b(callback);
         } else {
            console.log("failed a");
         }
     })
}

b будет выглядеть следующим образом:

function b(callback) {
      $.post(..., function(result){
         if(result){
            c(callback);
         } else {
            console.log("failed b");
         }
     })
}

И c будет выглядеть так:

function c(callback) {
     $.post(..., function(result){
         if(result){
            console.log("successful");
         } else {
            console.log("failed b");
         }
         callback();
     })
}

Это позволяет нам знать, когда цикл завершен. Что позволяет нам писать:

var data = [{param1 : 1235, param2: 3214},  {param1 : 5432, param2: 9876}];

var index = 0;
(function updateData(){
    a(data[index].param1,data[index].param2,function(){ //call a with the data
        index++;//update the index 
        updateData(); // start the next cycle
    });
});

Ответ 5

Вот замечательно простой и очень эффективный плагин AJAX chaining/queue. Он будет выполнять ваши ajax-методы последовательно друг за другом.

Он работает, принимая массив методов, а затем выполняет их последовательно. Он не выполнит следующий метод, ожидая ответа.

//--- ЭТО ЧАСТЬ - ВАШ КОД: -----------------------

$(document).ready(function() {

var AjaxQ = [];
AjaxQ[0] = function () { AjaxMethod1(); }
AjaxQ[1] = function () { AjaxMethod2(); }
AjaxQ[3] = function () { AjaxMethod3(); }

//Execute methods in sequence
$(document).sc_ExecuteAjaxQ({ fx: AjaxQ });

});

//--- ЭТО ЧАСТЬ - ПЛАН. AJAX -------------------

$. fn.sc_ExecuteAjaxQ = function (options) {

//? Executes a series of AJAX methods in dequence

var options = $.extend({

    fx: [] //function1 () { }, function2 () { }, function3 () { }

}, options);

if (options.fx.length > 0) {

    var i = 0;

    $(this).unbind('ajaxComplete');
    $(this).ajaxComplete(function () {

        i++;
        if (i < options.fx.length && (typeof options.fx[i] == "function")) { options.fx[i](); }
        else { $(this).unbind('ajaxComplete'); }

    });

    //Execute first item in queue
    if (typeof options.fx[i] == "function") { options.fx[i](); }
    else { $(this).unbind('ajaxComplete'); }

} 

}