对ajax请求进行排序

IT技术 javascript jquery ajax design-patterns queue
2021-02-08 03:23:51

我发现有时需要迭代一些集合并对每个元素进行 ajax 调用。我希望每次调用都在移动到下一个元素之前返回,这样我就不会向服务器发送请求 - 这通常会导致其他问题。而且我不想将 async 设置为 false 并冻结浏览器。

通常这涉及设置某种迭代器上下文,我会在每次成功回调时通过该上下文。我认为必须有一种更清洁更简单的方法?

有没有人有一个聪明的设计模式来如何巧妙地处理一个为每个项目进行 ajax 调用的集合?

6个回答

jQuery 1.5+

我开发了一个$.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

如果您使用 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 }));
        }
    });
});

jsfiddle 演示- 1.4 版本

如果 ajax url 依赖于前一个 ajax 调用的返回数据怎么办?任何想法如何使它起作用?
2021-03-15 03:23:51
@dredrik - 不,javascript 在基于函数的范围内处理变量......oldComplete每次调用都不同$.ajaxQueue()
2021-03-24 03:23:51
有什么方法可以知道队列完全为空/完成并触发操作?
2021-04-01 03:23:51
如果发送多个请求,oldComplete 不会被覆盖吗?
2021-04-02 03:23:51
这对我很有用。一个问题@gnarf,假设 abort 方法是从队列中删除 ajax 请求,我是否正确?如果有,如何称呼?我试过 $.ajaxQueue.abort();
2021-04-10 03:23:51

使用延迟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()