jQuery的摆脱嵌套的AJAX功能嵌套、功能、jQuery、AJAX

2023-09-10 14:54:13 作者:凉夜旧街深港少年与蓝海i

在我的JS,我需要得到3个文件的内容与AJAX,然后做一些code。这导致了嵌套的异步功能的一个相当奇怪的看着创作。另外随时随地我正与异步功能,这丑陋的嵌套出现。

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 working with async functions, this ugly nesting comes up.

如何避免嵌套函数时,我真的只是想等待他们每完成? (我使用jQuery,如果有帮助)

How can I avoid nesting functions when I really just want to wait for them each to complete? (I'm using jQuery if it helps)

 function loadFilesAndDoStuff() {
    $.get(firstFile, function(first_file_data) {
      $.get(secondFile, function(second_file_data) {
        $.get(thirdFile, function(third_file_data) {
          someOtherAsyncFunction(function(combined_file_data) {
            // do some stuff with the "combined_file_data".
          });
        });
      });
    });
  }

推荐答案

下面是有和没有使用deferreds的几种不同的技术。在任何情况下,所有的Ajax调用发起,然后一些片code跟踪,当所有的Ajax调用已经完成,并收集来自呼叫的数据,因为他们完成,所以当最后一个完成所有的数据可用

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.

您可以发动一次所有三个Ajax调用,只是检查每个完成功能,如果他们都还没有完成,并将结果存储在一个局部变量,直到他们完成所有的工作:

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

使用Deferreds,你可以这样做:

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

或者,使用deferreds从sucess处理每个推迟保存参数实际上是一个更短的版本:

Ajax和JQuery

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

}

这最后一个选项

工作演示: http://jsfiddle.net/jfriend00/5ppU4/

仅供参考,有没有神奇的 $内。当()。如果你看一下jQuery的$ C $下它,它只是保持时的参数传递给它的柜台都做(类似我的头两个选择这里)。主要的区别是,它使用的承诺接口jqXHR对象,而不是知识,它是一个Ajax调用。但在概念上,它在做同样的事情。

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.

下面多了一个使用我已经为处理多个deferreds写了一个新的对象的。

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

在MultiDeferred code是一个jQuery插件,专门用来处理通知你当多个deferreds完成和code因为它是在这里:

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