Sequenciamento de pedidos de ajax

Eu acho que às vezes preciso iterar algumas collections e fazer uma chamada ajax para cada elemento. Quero que cada chamada retorne antes de passar para o próximo elemento, para que eu não exploda o servidor com solicitações, o que geralmente leva a outros problemas. E eu não quero definir async para false e congelar o navegador.

Geralmente, isso envolve a configuração de algum tipo de contexto de iterador pelo qual passo a cada retorno de chamada de sucesso. Eu acho que deve haver um jeito mais simples e limpo?

Alguém tem um padrão de design inteligente para saber como ordenadamente trabalhar através de uma coleção fazendo chamadas ajax para cada item?

    jQuery 1.5+

    Desenvolvi um plug-in $.ajaxQueue() que usa o $.Deferred , .queue() e $.ajax() para também devolver uma promise que é resolvida quando a solicitação é concluída.

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

    Se você estiver usando o jQuery 1.4, poderá utilizar a fila de animação em um object vazio para criar sua própria “fila” para suas solicitações de ajax para os elementos.

    Você pode até fatorar isso em sua própria substituição $.ajax() . Este plugin $.ajaxQueue() usa a fila padrão ‘fx’ para jQuery, que irá iniciar automaticamente o primeiro elemento adicionado se a fila ainda não estiver em execução.

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

    Exemplo de uso

    Então, temos um

      que tem algum

    • que queremos copiar (usando ajax!) Para o

         // 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($("
      • ", { html: data })); } }); });
      • demonstração jsfiddle – versão 1.4

        Uma solução rápida e pequena usando promises diferidas. Embora isso use $.Deferred do jQuery, qualquer outro deve fazer.

         var Queue = function () { var previous = new $.Deferred().resolve(); return function (fn, fail) { return previous = previous.then(fn, fail || fn); }; }; 

        Uso, ligue para criar novas filas:

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

        Veja o exemplo com uma comparação lado a lado de solicitações assíncronas.

        Você pode envolver toda essa complexidade em uma function para fazer uma chamada simples que se parece com isso:

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

        Abaixo está um esboço (exemplo de trabalho, exceto a chamada ajax). Isso pode ser modificado para usar uma estrutura semelhante à fila em vez de uma matriz

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

        Idealmente, uma co-rotina com vários pontos de input, para que cada chamada de retorno do servidor possa chamar a mesma co-rotina será bem-vinda. Porra, isso está prestes a ser implementado no Javascript 1.7.

        Deixe-me tentar usar o fechamento …

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

        Sim, enquanto as outras respostas vão funcionar, elas são muito código e bagunça. O Frame.js foi projetado para lidar de maneira elegante com essa situação. https://github.com/bishopZ/Frame.js

        Por exemplo, isso fará com que a maioria dos navegadores seja interrompida:

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

        Enquanto isto não irá:

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

        Além disso, o uso do Frame permite que você elimine os objects de resposta e resolva todos eles após a conclusão da série completa de solicitações AJAX (se desejar):

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

        Eu uso http://developer.yahoo.com/yui/3/io/#queue para obter essa funcionalidade.

        As únicas soluções que posso encontrar são, como você diz, manter uma lista de chamadas / retornos de chamada pendentes. Ou aninhar a próxima chamada no retorno de chamada anterior, mas isso parece um pouco confuso.

        Você pode conseguir a mesma coisa usando 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 + '
        '); if(file) return $.get(file).then(nextFile); }

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

        Estou postando esta resposta pensando que poderia ajudar outras pessoas no futuro, procurando por algumas soluções simples no mesmo cenário.

        Isso agora é possível também usando o suporte a promises nativas introduzido no ES6. Você pode envolver a chamada do ajax em uma promise e retorná-la ao manipulador do elemento.

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

        Agora chame a function recursivamente, de onde você tem a coleção dos elementos.

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

        Eu sugeriria uma abordagem um pouco mais sofisticada, que é reutilizável para casos diferentes.
        Eu estou usando, por exemplo, quando eu preciso desacelerar uma seqüência de chamadas quando o usuário está digitando no editor de texto.

        Mas tenho certeza de que também deve funcionar ao percorrer a coleção. Nesse caso, ele pode enfileirar solicitações e enviar uma única chamada AJAX em vez de 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++; } }