Chain ajax and execute it in sequence. Jquery Deferred

大憨熊 提交于 2019-12-17 08:59:38

问题


I have 3 processes that needs ajax to complete. But it is asynchronous and it fails to do what I wanted to do..

Lets say:

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");
         }
     })
}

I want it to execute like this

a
b
c

That code will work perfectly, as you can see.. but if use a loop.

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

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

It will not work as expected and will just do:

a
a
b
b
c
c

instead of

a
b
c
a
b
c

回答1:


There are many ways to write this kind of thing.

A flexible approach is separate "actions" from "sequence", allowing :

  • functions a, b, c to initiate an asynchronous (ajax) action, with no knowledge of how they are to be sequenced
  • a, b, c to be reusable, as part of one or more sequences or individually, as required.

Here's a way to code this approach, using .then() exclusively for the chaining logic :

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);
});

Alternatively, if you want to have a single asynchronous function, a, called from within a loop then the code could be something like this :

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()'s 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:


You can chain asynchronous calls like ajax calls using jQuery's deferred object and using 'then'.

You can also change it to use functions that returns a deferred promise object, instead of an ajax call as I have in my example.

http://jsfiddle.net/q4cFv/

(Example with async function: 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:


I see that c doesn't depend of b result, and b doesn't depend of a result.

Following the GRASP principles (http://en.wikipedia.org/wiki/GRASP_(object-oriented_design), a mustn't know b and b mustn't know c.

When we program, to remember the GRASP principles or guidelines is very important.

High Cohesion and Low Coupling mean that our code will be better, more reusable and easier to maintain.

The main function which knows a, b, and c must build the chained calls.

The functions would be:

    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");
    }

The main function would be:

    // 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 doesn't exist in jQuery, so I have built it. It works with jQuery 1.8 and later versions.

$.iterativeWhen = function () {

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

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

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

    deferred.resolve();

    return promise;
};

The mockPost function simulates a call to $.post with a success probability:

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();
    }

The log output is:

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 here: http://jsfiddle.net/E2tp3/




回答4:


Your problem is that you're calling all the a s at once but you want to wait for the first cycle before going to the next. You want to wait for the previous 'a' cycle to finish before you start the next cycle.

Let's assume a,b,c accept a callback, and pass it on,

a would look like

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

b would go like:

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

And c would look like:

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

This lets us know when a cycle is complete. Which lets us write:

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:


Here is an wonderfully simple and highly effect AJAX chaining / queue plugin. It will execute you ajax methods in sequence after each other.

It works by accepting an array of methods and then executing them in sequence. It wont execute the next method whilst waiting for a response.

//--- THIS PART IS YOUR CODE -----------------------

$(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 });

});

//--- THIS PART IS THE AJAX PLUGIN -------------------

$.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'); }

} 

}



来源:https://stackoverflow.com/questions/16384841/chain-ajax-and-execute-it-in-sequence-jquery-deferred

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!