68 votes

Séquençage des requêtes ajax

Je constate que j'ai parfois besoin d'itérer une collection et de faire un appel ajax pour chaque élément. Je veux que chaque appel revienne avant de passer à l'élément suivant afin de ne pas inonder le serveur de requêtes, ce qui entraîne 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 d'itération que je parcours à chaque rappel de réussite. Je pense qu'il doit y avoir un moyen plus propre et plus simple ?

Quelqu'un a-t-il un modèle de conception astucieux pour travailler proprement à travers une collection en faisant des appels ajax pour chaque élément ?

1 votes

La marée et le temps passent (comme @gnarf l'a fait remarquer) ... depuis la version 1.5, jQuery dispose de toute une série d'outils de gestion de l'environnement. Deferred les objets comprenant when() qui conviennent parfaitement à cette situation. Voir : api.jquery.com/category/deferred-object et api.jquery.com/jQuery.when

119voto

gnarf Points 49213

JQuery 1.5+.

J'ai développé un $.ajaxQueue() qui utilise le $.Deferred , .queue() et $.ajax() pour renvoyer également un promesse qui est résolu 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 la 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 $.ajax() remplacement. Ce plugin $.ajaxQueue() utilise la file d'attente standard "fx" de 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'utilisation

Donc, nous avons un <ul id="items"> qui a des <li> que nous voulons copier (en utilisant l'ajax !) dans le fichier <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 }));
        }
    });
});

démonstration de jsfiddle - Version 1.4

0 votes

Si l'on envoie plus d'une demande, l'ancienComplete ne sera-t-il pas écrasé ?

0 votes

@dredrik - Non, javascript gère les variables dans une portée basée sur les fonctions... oldComplete est différent pour chaque appel à $.ajaxQueue()

0 votes

Que faire si l'url ajax dépend des données de retour de l'appel ajax précédent ? Une idée pour que cela fonctionne ?

14voto

Thomas Nadin Points 683

Une solution rapide et modeste utilisant des promesses différées. Bien que cette solution utilise l'algorithme de jQuery $.Deferred n'importe quel autre devrait faire l'affaire.

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 demandes asynchrones.

3voto

naikus Points 11284

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

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

Voici une ébauche (exemple de travail, sauf l'appel ajax). Ceci peut être modifié pour utiliser une structure de type file d'attente 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
     }
  };

3voto

DonnieKun Points 53

Idéalement, une coroutine avec de multiples points d'entrée pour que chaque callback du serveur puisse appeler la même coroutine serait parfaite. Mince, c'est sur le point d'être implémenté dans Javascript 1.7.

Laissez-moi essayer d'utiliser 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);

2voto

BishopZ Points 2112

Oui, bien que les autres réponses fonctionnent, elles nécessitent beaucoup de code et sont désordonnées. Frame.js a été conçu pour répondre élégamment à cette situation. https://github.com/bishopZ/Frame.js

Par exemple, la plupart des navigateurs seront bloqués :

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

Alors que ce ne sera pas le cas :

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

De plus, l'utilisation de Frame vous permet de mettre en cascade les objets de réponse et de les traiter tous après la fin de la série complète de requêtes AJAX (si vous le souhaitez) :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X