jQuery getting rid of nested ajax functions

前端 未结 2 1439
臣服心动
臣服心动 2021-01-07 13:17

In my JS, I need to get the contents of 3 files with AJAX, then do some code. This has led to a rather strange looking creation of nested async functions. Also anytime I\'m

2条回答
  •  隐瞒了意图╮
    2021-01-07 13:30

    Here are several different techniques with and without the use of deferreds. In all cases, all the ajax calls are launched and then some piece of code keeps track of when all the ajax calls have completed and collects the data from the calls as they complete so when the last one completes all the data is available.

    You can launch all three ajax calls at once and just check in each completion function if they are all done yet, storing the results in a local variable until they are all done:

    function loadFilesAndDoStuff() {
        var cntr = 3;
        var data1, data2, data3;
    
        function checkDone() {
            --cntr;
            if (cntr === 0) {
                // all three are done here
                someOtherFunction(combined_file_data);
            }
        }
    
        $.get(firstFile, function(data) {
            data1 = data;
            checkDone();
        });
        $.get(secondFile, function(data) {
            data2 = data;
            checkDone();
        });
        $.get(thirdFile, function(data) {
            data3 = data;
            checkDone();
        });
     }
    

    Or, you can put more of it in a common function and pass the array of filenames to the function:

    function loadFilesAndDoStuff(filesArray) {
        var results = [];
        var doneCnt = 0;
    
        function checkDone(index, data) {
            results[index] = data;
            ++doneCnt;
            if (doneCnt === filesArray.length) {
                // all results are in the results array now
            }
        }
    
        for (var i = 0; i < filesArray.length; i++) {
            results.push(null);
            $.get(filesArray[i], checkDone.bind(this, i));
        }
     }
    

    Using Deferreds, you can do this:

    function loadFilesAndDoStuff(filesArray) {
        var results = [];
        var deferreds = [];
    
        function doneOne(index, data) {
            results[index] = data;
        }
    
        for (var i = 0; i < filesArray.length; i++) {
            results.push(null);
            deferreds.push($.get(filesArray[i], doneOne.bind(this, i)));
        }
        $.when.apply($, deferreds).done(function() {
            // all ajax calls are done and results are available now
        });
    }
    

    Or, an even shorter version using the fact that deferreds save the arguments from the sucess handlers for each deferred:

    function loadFilesAndDoStuff(filesArray) {
        var deferreds = [];
    
        for (var i = 0; i < filesArray.length; i++) {
            deferreds.push($.get(filesArray[i]));
        }
        $.when.apply($, deferreds).done(function() {
            // all ajax calls are done and results are available now
            // arguments[0][0] is the data from the first $.get call
            // arguments[1][0] is the data from the second $.get call
            // and so on
        });
    
    }
    

    Working demo of this last option: http://jsfiddle.net/jfriend00/5ppU4/

    FYI, there's no magic inside of $.when(). If you look at the jQuery code for it, it is just keeping a counter of when the arguments passed to it are all done (similar to my first two options here). The main difference is that it's using the promise interface to the jqXHR object instead of knowledge that it's an ajax call. But conceptually, it's doing the same thing.


    Here's one more using a new object I've written for handling multiple deferreds.

    function loadFilesAndDoStuff(filesArray) {
        var deferred = $.MultiDeferred().done(function() {
            // all ajax calls are done and results are available now
            // arguments[0][0] is the data from the first $.get call
            // arguments[1][0] is the data from the second $.get call
            // and so on
        });
    
        for (var i = 0; i < filesArray.length; i++) {
            deferred.add($.get(filesArray[i]));
        }
    }
    

    The MultiDeferred code is a jQuery plug-in specifically written to handle notifying you when multiple deferreds are done and the code for it is here:

    jQuery.MultiDeferred = function(/* zero or more promises */) {
    
        // make the Deferred
        var self = jQuery.Deferred();
    
        var remainingToFinish = 0;
        var promises = [];
        var args = [];
        var anyFail = false;
        var failImmediate = false;
    
        function _add(p) {
            // save our index in a local variable so it's available in this closure later
            var index = promises.length;
    
            // save this promise
            promises.push(p);
            // push placeholder in the args array
            args.push([null]);
    
            // one more waiting to finish
            ++remainingToFinish;
    
            // see if all the promises are done
            function checkDone(fail) {
                return function() {
                    anyFail |= fail;
                    // make copy of arguments so we can save them
                    args[index] = Array.prototype.slice.call(arguments, 0);
                    --remainingToFinish;
    
                    // send notification that one has finished
                    self.notify.apply(self, args[index]);
                    // if all promises are done, then resolve or reject
                    if (self.state() === "pending" && (remainingToFinish === 0 || (fail && failImmediate))){
                        var method = anyFail ? "reject" : "resolve";
                        self[method].apply(self, args);
                    }
                }
            }
            // add our own monitors so we can collect all the data
            // and keep track of whether any fail
            p.done(checkDone(false)).fail(checkDone(true));
        }
    
        // add a new promise
        self.add = function(/* one or more promises or arrays of promises */) {
            if (this.state() !== "pending") {
                throw "Can't add to a deferred that is already resolved or rejected";
            }
            for (var i = 0; i < arguments.length; i++) {
                if (arguments[i] instanceof Array) {
                    for (var j = 0; j < arguments[i].length; j++) {
                        _add(arguments[i][j]);
                    }
                } else {
                    _add(arguments[i]);
                }
            }
            return this;
        }
    
        // get count of remaining promises that haven't completed yet
        self.getRemaining = function() {
            return remainingToFinish;
        }
    
        // get boolean on whether any promises have failed yet
        self.getFailYet = function() {
            return anyFail;
        }
    
        self.setFailImmediate = function(failQuick) {
            failImmediate = failQuick;
            return this;
        }
    
        // now process all the arguments
        for (var i = 0; i < arguments.length; i++) {
            self.add(arguments[i]);
        }
        return self;    
    };
    

提交回复
热议问题