我发现有时需要迭代一些集合并对每个元素进行 ajax 调用。我希望每次调用都在移动到下一个元素之前返回,这样我就不会向服务器发送请求 - 这通常会导致其他问题。而且我不想将 async 设置为 false 并冻结浏览器。
通常这涉及设置某种迭代器上下文,我会在每次成功回调时通过该上下文。我认为必须有一种更清洁更简单的方法?
有没有人有一个聪明的设计模式来如何巧妙地处理一个为每个项目进行 ajax 调用的集合?
我发现有时需要迭代一些集合并对每个元素进行 ajax 调用。我希望每次调用都在移动到下一个元素之前返回,这样我就不会向服务器发送请求 - 这通常会导致其他问题。而且我不想将 async 设置为 false 并冻结浏览器。
通常这涉及设置某种迭代器上下文,我会在每次成功回调时通过该上下文。我认为必须有一种更清洁更简单的方法?
有没有人有一个聪明的设计模式来如何巧妙地处理一个为每个项目进行 ajax 调用的集合?
我开发了一个$.ajaxQueue()
插件,它使用$.Deferred
, .queue()
, 并且$.ajax()
还传回在请求完成时解决的Promise。
/*
* jQuery.ajaxQueue - A queue for ajax requests
*
* (c) 2011 Corey Frang
* Dual licensed under the MIT and GPL licenses.
*
* Requires jQuery 1.5+
*/
(function($) {
// jQuery on an empty object, we are going to use this as our Queue
var ajaxQueue = $({});
$.ajaxQueue = function( ajaxOpts ) {
var jqXHR,
dfd = $.Deferred(),
promise = dfd.promise();
// queue our ajax request
ajaxQueue.queue( doRequest );
// add the abort method
promise.abort = function( statusText ) {
// proxy abort to the jqXHR if it is active
if ( jqXHR ) {
return jqXHR.abort( statusText );
}
// if there wasn't already a jqXHR we need to remove from queue
var queue = ajaxQueue.queue(),
index = $.inArray( doRequest, queue );
if ( index > -1 ) {
queue.splice( index, 1 );
}
// and then reject the deferred
dfd.rejectWith( ajaxOpts.context || ajaxOpts,
[ promise, statusText, "" ] );
return promise;
};
// run the actual query
function doRequest( next ) {
jqXHR = $.ajax( ajaxOpts )
.done( dfd.resolve )
.fail( dfd.reject )
.then( next, next );
}
return promise;
};
})(jQuery);
如果您使用 jQuery 1.4,您可以利用空对象上的动画队列为元素的 ajax 请求创建您自己的“队列”。
您甚至可以将其计入您自己的$.ajax()
替代品中。此插件$.ajaxQueue()
使用标准的 jQuery 'fx' 队列,如果队列尚未运行,它将自动启动第一个添加的元素。
(function($) {
// jQuery on an empty object, we are going to use this as our Queue
var ajaxQueue = $({});
$.ajaxQueue = function(ajaxOpts) {
// hold the original complete function
var oldComplete = ajaxOpts.complete;
// queue our ajax request
ajaxQueue.queue(function(next) {
// create a complete callback to fire the next event in the queue
ajaxOpts.complete = function() {
// fire the original complete if it was there
if (oldComplete) oldComplete.apply(this, arguments);
next(); // run the next query in the queue
};
// run the query
$.ajax(ajaxOpts);
});
};
})(jQuery);
所以,我们有<ul id="items">
一些<li>
我们想要复制(使用 ajax!)到<ul id="output">
// get each item we want to copy
$("#items li").each(function(idx) {
// queue up an ajax request
$.ajaxQueue({
url: '/echo/html/',
data: {html : "["+idx+"] "+$(this).html()},
type: 'POST',
success: function(data) {
// Write to #output
$("#output").append($("<li>", { html: data }));
}
});
});
使用延迟Promise的快速小型解决方案。尽管这使用了 jQuery 的$.Deferred
,但任何其他都应该这样做。
var Queue = function () {
var previous = new $.Deferred().resolve();
return function (fn, fail) {
return previous = previous.then(fn, fail || fn);
};
};
用法,调用创建新队列:
var queue = Queue();
// Queue empty, will start immediately
queue(function () {
return $.get('/first');
});
// Will begin when the first has finished
queue(function() {
return $.get('/second');
});
请参阅并排比较异步请求的示例。
您可以将所有复杂性包装到一个函数中,以进行如下所示的简单调用:
loadSequantially(['/a', '/a/b', 'a/b/c'], function() {alert('all loaded')});
下面是一个粗略的草图(工作示例,除了 ajax 调用)。这可以修改为使用类似队列的结构而不是数组
// load sequentially the given array of URLs and call 'funCallback' when all's done
function loadSequantially(arrUrls, funCallback) {
var idx = 0;
// callback function that is called when individual ajax call is done
// internally calls next ajax URL in the sequence, or if there aren't any left,
// calls the final user specified callback function
var individualLoadCallback = function() {
if(++idx >= arrUrls.length) {
doCallback(arrUrls, funCallback);
}else {
loadInternal();
}
};
// makes the ajax call
var loadInternal = function() {
if(arrUrls.length > 0) {
ajaxCall(arrUrls[idx], individualLoadCallback);
}else {
doCallback(arrUrls, funCallback);
}
};
loadInternal();
};
// dummy function replace with actual ajax call
function ajaxCall(url, funCallBack) {
alert(url)
funCallBack();
};
// final callback when everything's loaded
function doCallback(arrUrls, func) {
try {
func();
}catch(err) {
// handle errors
}
};
理想情况下,具有多个入口点的协程,因此来自服务器的每个回调都可以调用相同的协程。该死,这将在 Javascript 1.7 中实现。
让我尝试使用闭包...
function BlockingAjaxCall (URL,arr,AjaxCall,OriginalCallBack)
{
var nextindex = function()
{
var i =0;
return function()
{
return i++;
}
};
var AjaxCallRecursive = function(){
var currentindex = nextindex();
AjaxCall
(
URL,
arr[currentindex],
function()
{
OriginalCallBack();
if (currentindex < arr.length)
{
AjaxCallRecursive();
}
}
);
};
AjaxCallRecursive();
}
// suppose you always call Ajax like AjaxCall(URL,element,callback) you will do it this way
BlockingAjaxCall(URL,myArray,AjaxCall,CallBack);
是的,虽然其他答案会起作用,但它们有很多代码并且看起来很乱。Frame.js 旨在优雅地解决这种情况。 https://github.com/bishopZ/Frame.js
例如,这将导致大多数浏览器挂起:
for(var i=0; i<1000; i++){
$.ajax('myserver.api', { data:i, type:'post' });
}
虽然这不会:
for(var i=0; i<1000; i++){
Frame(function(callback){
$.ajax('myserver.api', { data:i, type:'post', complete:callback });
});
}
Frame.start();
此外,使用 Frame 允许您对响应对象进行瀑布式处理,并在整个 AJAX 请求系列完成后处理它们(如果您愿意):
var listOfAjaxObjects = [ {}, {}, ... ]; // an array of objects for $.ajax
$.each(listOfAjaxObjects, function(i, item){
Frame(function(nextFrame){
item.complete = function(response){
// do stuff with this response or wait until end
nextFrame(response); // ajax response objects will waterfall to the next Frame()
$.ajax(item);
});
});
Frame(function(callback){ // runs after all the AJAX requests have returned
var ajaxResponses = [];
$.each(arguments, function(i, arg){
if(i!==0){ // the first argument is always the callback function
ajaxResponses.push(arg);
}
});
// do stuff with the responses from your AJAX requests
// if an AJAX request returned an error, the error object will be present in place of the response object
callback();
});
Frame.start()