Séquence des requêtes ajax

je trouve que j'ai parfois besoin d'itérer une collecte et de faire un appel ajax pour chaque élément. Je veux que chaque appel revienne avant de passer à l'élément suivant pour que je n'explose pas le serveur avec des requêtes - ce qui conduit souvent à d'autres problèmes. Et je ne veux pas mettre async à false et geler le navigateur.

Habituellement, cela implique la mise en place d'une sorte de contexte itérateur que je passe à travers chaque succès de rappel. Je pense qu'il doit y avoir un moyen plus simple de nettoyer?

est-ce que quelqu'un a un modèle de conception astucieux pour la façon de travailler correctement à travers une collection faire des appels ajax pour chaque élément?

60
demandé sur gnarf 2010-06-14 07:51:12

9 réponses

jQuery 1.5+

j'ai développé un $.ajaxQueue() plugin qui utilise le $.Deferred , .queue() , et $.ajax() pour transmettre aussi une promesse qui est résolue lorsque la demande est terminée.

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

si vous utilisez jQuery 1.4, vous pouvez utiliser le file d'attente d'animation sur un objet vide pour créer votre propre file d'attente pour vos requêtes ajax pour les éléments.

vous pouvez même en tenir compte dans votre propre remplacement $.ajax() . Ce plugin $.ajaxQueue() utilise la file d'attente standard 'fx' pour jQuery, qui démarre automatiquement le premier élément ajouté si la file d'attente n'est pas déjà en cours d'exécution.

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

Exemple D'Usage

donc, nous avons un <ul id="items"> qui a quelque <li> que nous voulons copier (en utilisant ajax!) à la <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 démonstration - version 1.4

104
répondu gnarf 2013-08-25 20:43:39

une solution rapide et petite en utilisant des promesses différées. Bien que cela utilise le $.Deferred de jQuery , n'importe quel autre devrait faire.

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

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

utilisation, appel pour créer de nouvelles files d'attente:

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

voir l'exemple avec une comparaison côte à côte des requêtes asynchrones.

13
répondu Thomas Nadin 2013-12-17 14:14:29

vous pouvez envelopper toute cette complexité dans une fonction pour faire un appel simple qui ressemble à ceci:

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

ci-dessous est un croquis grossier (exemple de travail, sauf l'appel ajax). Ceci peut être modifié pour utiliser une structure de type queue au lieu d'un tableau

  // 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
     }
  };
3
répondu naikus 2010-06-14 04:39:21

idéalement, une coroutine avec plusieurs points d'entrée de sorte que chaque rappel du serveur peut appeler la même coroutine sera soigné. Merde, C'est sur le point d'être implémenté en Javascript 1.7.

laissez-moi essayer la fermeture...

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);
3
répondu DonnieKun 2010-06-14 06:10:26

Ouais, tandis que les autres réponses, elles sont beaucoup de code et désordonné. Cadre.js a été conçu pour aborder avec élégance cette situation. https://github.com/bishopZ/Frame.js

par exemple, cela causera la suspension de la plupart des navigateurs:

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

alors que ce ne sera pas:

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

aussi, L'utilisation de cadre vous permet de waterfall les objets de réponse et de traiter avec eux tous après le toute la série de la demande AJAX ont complété (si vous voulez):

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()
2
répondu BishopZ 2012-04-07 23:15:17

j'utilise http://developer.yahoo.com/yui/3/io/#queue pour obtenir cette fonctionnalité.

les seules solutions que je peux trouver sont, comme vous le dites, la tenue d'une liste des appels / rappels en attente. Ou imiter le prochain appel dans le rappel précédent, mais ça sent un peu le désordre.

1
répondu unomi 2010-06-14 04:40:24

vous pouvez obtenir la même chose en utilisant 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

1
répondu Shanimal 2016-01-08 08:11:27

je poste cette réponse pensant qu'elle pourrait aider d'autres personnes à l'avenir, à la recherche de quelques solutions simples dans le même scénario.

c'est maintenant possible aussi en utilisant le support natif de promesse introduit dans ES6. Vous pouvez placer l'appel ajax dans une promesse et de le retourner au gestionnaire de l'élément.

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

    });
}

appelez maintenant la fonction de façon récursive, d'où vous avez la collection des éléments.

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;
    }
}
1
répondu Sandip Ghosh 2018-01-05 07:02:20

je suggère une approche un peu plus sophistiquée qui est réutilisable pour différents cas.

Je l'utilise pour l'exemple, quand j'ai besoin de ralentir une séquence d'appel lorsque l'utilisateur tape dans l'éditeur de texte.

mais je suis sûr qu'il devrait également fonctionner lors de l'itération à travers la collection. Dans ce cas, il peut mettre en file d'attente les requêtes et envoyer un seul appel AJAX au lieu 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++;
    }
}
1
répondu Oleg Lazaryev 2018-08-19 13:53:04