在我的JS中,我需要使用AJAX获取3个文件的内容,然后执行一些代码。这导致嵌套异步函数的外观看起来很奇怪。同样在我使用异步功能的任何时候,都会出现这种丑陋的嵌套。
当我真的只想等待它们完成时,如何避免嵌套函数?(如果有帮助,我正在使用jQuery)
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". }); }); }); }); }
这有几种使用和不使用延迟的不同技术。在所有情况下,都会启动所有ajax调用,然后一段代码跟踪所有ajax调用何时完成,并在调用完成时收集数据,以便在最后一个调用完成时提供所有数据。
您可以一次启动所有三个ajax调用,只需检查每个完成函数是否全部完成,然后将结果存储在本地变量中,直到全部完成:
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(); }); }
或者,您可以将其放在通用函数中,然后将文件名数组传递给该函数:
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,您可以执行以下操作:
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 }); }
或者,一个更短的版本,它使用延迟的事实为每个延迟的对象保存来自成功处理程序的参数:
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/
仅供参考,里面没有魔术$.when()。如果您查看它的jQuery代码,它只是在保留传递给它的参数何时完成的计数器(类似于此处的前两个选项)。主要区别在于,它使用的是jqXHR对象的promise接口,而不是知道它是ajax调用。但是从概念上讲,它在做同样的事情。
$.when()
这是使用我编写的用于处理多个延迟的新对象的另一个方法。
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代码是一个jQuery插件,专门用于处理多个延迟时通知您,其代码在此处:
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; };