Javascript 对ajax请求进行排序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3034874/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 03:01:10  来源:igfitidea点击:

Sequencing ajax requests

javascriptjqueryajaxdesign-patternsqueue

提问by Scott Evernden

I find I sometimes need to iterate some collection and make an ajax call for each element. I want each call to return before moving to the next element so that I don't blast the server with requests - which often leads to other issues. And I don't want to set async to false and freeze the browser.

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

Usually this involves setting up some kind of iterator context that i step thru upon each success callback. I think there must be a cleaner simpler way?

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

Does anyone have a clever design pattern for how to neatly work thru a collection making ajax calls for each item?

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

回答by gnarf

jQuery 1.5+

jQuery 1.5+

I developed an $.ajaxQueue()plugin that uses the $.Deferred, .queue(), and $.ajax()to also pass back a promisethat is resolved when the request completes.

我开发了一个$.ajaxQueue()插件,它使用$.Deferred, .queue(), 并且$.ajax()还传回在请求完成时解决的承诺

/*
* 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

If you're using jQuery 1.4, you can utilize the animation queue on an empty object to create your own "queue" for your ajax requests for the elements.

如果您使用的是 jQuery 1.4,您可以利用空对象上的动画队列为元素的 ajax 请求创建您自己的“队列”。

You can even factor this into your own $.ajax()replacement. This plugin $.ajaxQueue()uses the standard 'fx' queue for jQuery, which will auto-start the first added element if the queue isn't already running.

您甚至可以将其计入您自己的$.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);

Example Usage

示例用法

So, we have a <ul id="items">which has some <li>that we want to copy (using ajax!) to the <ul id="output">

因此,我们有一个<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 demonstration- 1.4 version

jsfiddle 演示- 1.4 版本

回答by Thomas Nadin

A quick and small solution using deferred promises. Although this uses jQuery's $.Deferred, any other should do.

使用延迟承诺的快速和小型解决方案。尽管这使用了 jQuery 的$.Deferred,但任何其他都应该这样做。

var Queue = function () {
    var previous = new $.Deferred().resolve();

    return function (fn, fail) {
        return previous = previous.then(fn, fail || fn);
    };
};

Usage, call to create new queues:

用法,调用创建新队列:

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

See the examplewith a side-by-side comparison of asynchronous requests.

请参阅并排比较异步请求的示例

回答by naikus

You can wrap all that complexity into a function to make a simple call that looks like this:

您可以将所有复杂性包装到一个函数中,以进行如下所示的简单调用:

loadSequantially(['/a', '/a/b', 'a/b/c'], function() {alert('all loaded')});

Below is a rough sketch (working example, except the ajax call). This can be modified to use a queue-like structure instead of an array

下面是一个粗略的草图(工作示例,除了 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
     }
  };

回答by DonnieKun

Ideally, a coroutine with multiple entry points so every callback from server can call the same coroutine will be neat. Damn, this is about to be implemented in Javascript 1.7.

理想情况下,具有多个入口点的协程,因此来自服务器的每个回调都可以调用相同的协程。该死,这将在 Javascript 1.7 中实现。

Let me try using closure...

让我尝试使用闭包...

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

回答by BishopZ

Yeah, while the other answers will work, they are lots of code and messy looking. Frame.js was designed to elegantly address this situation. https://github.com/bishopZ/Frame.js

是的,虽然其他答案有效,但它们有很多代码并且看起来很乱。Frame.js 旨在优雅地解决这种情况。 https://github.com/bishopZ/Frame.js

For instance, this will cause most browsers to hang:

例如,这将导致大多数浏览器挂起:

for(var i=0; i<1000; i++){
    $.ajax('myserver.api', { data:i, type:'post' });
}

While this will not:

虽然这不会:

for(var i=0; i<1000; i++){
    Frame(function(callback){
        $.ajax('myserver.api', { data:i, type:'post', complete:callback });
    });
}
Frame.start();

Also, using Frame allows you to waterfall the response objects and deal with them all after the entire series of AJAX request have completed (if you want to):

此外,使用 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()

回答by Sandip Ghosh

I am posting this answer thinking that it might help other persons in future, looking for some simple solutions in the same scenario.

我发布这个答案认为它可能会在未来帮助其他人,在同一场景中寻找一些简单的解决方案。

This is now possible also using the native promise support introduced in ES6. You can wrap the ajax call in a promise and return it to the handler of the element.

现在也可以使用 ES6 中引入的原生 promise 支持。您可以将 ajax 调用包装在一个承诺中,并将其返回给元素的处理程序。

function ajaxPromise(elInfo) {
    return new Promise(function (resolve, reject) {
        //Do anything as desired with the elInfo passed as parameter

        $.ajax({
            type: "POST",
            url: '/someurl/',
            data: {data: "somedata" + elInfo},
            success: function (data) {
                //Do anything as desired with the data received from the server,
                //and then resolve the promise
                resolve();
            },
            error: function (err) {
                reject(err);
            },
            async: true
        });

    });
}

Now call the function recursively, from where you have the collection of the elements.

现在递归调用该函数,从您拥有元素集合的地方开始。

function callAjaxSynchronous(elCollection) {
    if (elCollection.length > 0) {
        var el = elCollection.shift();
        ajaxPromise(el)
        .then(function () {
            callAjaxSynchronous(elCollection);
        })
        .catch(function (err) {
            //Abort further ajax calls/continue with the rest
            //callAjaxSynchronous(elCollection);
        });
    }
    else {
        return false;
    }
}

回答by Oleg Lazaryev

I would suggest a bit more sophisticated approach which is reusable for different cases.
I am using it for example when I need to slow down a call sequence when the user is typing in text editor.

我会建议一种更复杂的方法,它可以在不同的情况下重复使用。
例如,当用户在文本编辑器中输入时需要减慢调用顺序时,我就会使用它。

But I am sure it should also work when iterating through the collection. In this case it can queue requests and can send a single AJAX call instead of 12.

但我确信它在遍历集合时也应该有效。在这种情况下,它可以对请求进行排队,并且可以发送一个 AJAX 调用而不是 12 个。

queueing = {
    callTimeout:                 undefined,
    callTimeoutDelayTime:        1000,
    callTimeoutMaxQueueSize:     12,
    callTimeoutCurrentQueueSize: 0,

    queueCall: function (theCall) {
        clearTimeout(this.callTimeout);

        if (this.callTimeoutCurrentQueueSize >= this.callTimeoutMaxQueueSize) {
            theCall();
            this.callTimeoutCurrentQueueSize = 0;
        } else {
            var _self = this;

            this.callTimeout = setTimeout(function () {
                theCall();
                _self.callTimeoutCurrentQueueSize = 0;
            }, this.callTimeoutDelayTime);
        }

        this.callTimeoutCurrentQueueSize++;
    }
}

回答by Shanimal

You can achieve the same thing using then.

您可以使用then.

var files = [
  'example.txt',
  'example2.txt',
  'example.txt',
  'example2.txt',
  'example.txt',
  'example2.txt',
  'example2.txt',
  'example.txt'
];

nextFile().done(function(){
  console.log("done",arguments)
});

function nextFile(text){
  var file = files.shift();
  if(text)
    $('body').append(text + '<br/>');
  if(file)
    return $.get(file).then(nextFile);
}

http://plnkr.co/edit/meHQHU48zLTZZHMCtIHm?p=preview

http://plnkr.co/edit/meHQHU48zLTZZHMCtIHm?p=preview

回答by unomi

I use http://developer.yahoo.com/yui/3/io/#queueto get that functionality.

我使用http://developer.yahoo.com/yui/3/io/#queue来获得该功能。

The only solutions I can come up with is, as you say, maintaining a list of pending calls / callbacks. Or nesting the next call in the previous callback, but that feels a bit messy.

正如你所说,我能想出的唯一解决方案是维护一个待处理的调用/回调列表。或者将下一个调用嵌套在上一个回调中,但这感觉有点混乱。