web-dev-qa-db-fra.com

Mettre les requêtes ajax en file d'attente à l'aide de jQuery.queue ()

J'utilise jQuery.queue () pour la première fois et je ne l'ai pas encore bien compris. Quelqu'un pourrait-il s'il vous plaît indiquer ce que je fais mal?

En regardant dans firebug, je vois toujours mes demandes POST se déclencher en même temps. Je me demande donc si j'appelle dequeue () au mauvais endroit.

Aussi - comment puis-je obtenir la longueur de la file d'attente?

La raison pour laquelle j'ai besoin de mettre en file d'attente ces demandes est qu'il est déclenché en cliquant sur un bouton. Et il est possible pour l'utilisateur de cliquer sur plusieurs boutons en succession rapide.

J'ai essayé de dépouiller la structure de base de mon code:

$("a.button").click(function(){
   $(this).doAjax(params);
});

// method
doAjax:function(params){ 

   $(document).queue("myQueueName", function(){
     $.ajax({
       type: 'POST',
       url: 'whatever.html',
       params: params,
       success: function(data){
         doStuff;

         $(document).dequeue("myQueueName");
       }
     });
   });

}
49
MBax

Votre problème ici est que .ajax() déclenche une requête Ajax asynchrone en cours d'exécution. Cela signifie que .ajax() retourne immédiatement, non bloquant. Donc, votre file d'attente des fonctions, mais ils vont tirer presque en même temps, comme vous l'avez décrit.

Je ne pense pas que .queue() soit un bon endroit pour avoir des requêtes ajax, mais plutôt pour l'utilisation de fx methods. Vous avez besoin d'un gestionnaire simple.

var ajaxManager = (function() {
     var requests = [];

     return {
        addReq:  function(opt) {
            requests.Push(opt);
        },
        removeReq:  function(opt) {
            if( $.inArray(opt, requests) > -1 )
                requests.splice($.inArray(opt, requests), 1);
        },
        run: function() {
            var self = this,
                oriSuc;

            if( requests.length ) {
                oriSuc = requests[0].complete;

                requests[0].complete = function() {
                     if( typeof(oriSuc) === 'function' ) oriSuc();
                     requests.shift();
                     self.run.apply(self, []);
                };   

                $.ajax(requests[0]);
            } else {
              self.tid = setTimeout(function() {
                 self.run.apply(self, []);
              }, 1000);
            }
        },
        stop:  function() {
            requests = [];
            clearTimeout(this.tid);
        }
     };
}());

C'est loin d'être parfait, je veux juste montrer la voie à suivre. L’exemple ci-dessus pourrait être utilisé de la manière suivante:

$(function() {
    ajaxManager.run(); 

    $("a.button").click(function(){
       ajaxManager.addReq({
           type: 'POST',
           url: 'whatever.html',
           data: params,
           success: function(data){
              // do stuff
           }
       });
    });
});
82
jAndy

J'avais besoin de faire la même chose, alors j'ai pensé poster ma solution ici.

Ce que j'ai essentiellement, c'est une page qui répertorie les projets sur des étagères qui ont tous des critères distincts. Je souhaitais charger les étagères une par une plutôt que de donner un contenu plus rapide à l’utilisateur, qu’il pourrait consulter pendant le reste du chargement.

Fondamentalement, j'ai stocké l'ID de chaque étagère dans un tableau JS que j'utilise lorsque je les appelle depuis PHP.

J'ai ensuite créé une fonction récursive qui extraira le premier index du tableau à chaque fois qu'il sera appelé et demandera l'étagère pour l'identifiant popped. Une fois que j'ai la réponse de $.get() ou $.post() selon ce que je préfère utiliser, j'appelle alors la fonction récursive à partir du rappel.

Voici une élaboration en code:

// array of shelf IDs
var shelves = new Array(1,2,3,4);

// the recursive function
function getShelfRecursive() {

    // terminate if array exhausted
    if (shelves.length === 0)
        return;

    // pop top value
    var id = shelves[0];
    shelves.shift();

    // ajax request
    $.get('/get/shelf/' + id, function(){
         // call completed - so start next request
         getShelfRecursive();
    });
}

// fires off the first call
getShelfRecursive();
11
diggersworld

J'utilise ce code très simple pour empêcher les appels ajax de se "doubler". 

var dopostqueue = $({});
function doPost(string, callback)
{
    dopostqueue.queue(function()
    {
        $.ajax(
        {   
            type: 'POST',
            url: 'thephpfile.php',
            datatype: 'json',
            data: string,
            success:function(result) 
            {
                dopostqueue.dequeue();
                callback(JSON.parse(result));
            }
        })
    });
}

Si vous ne voulez pas que la file d'attente se gère elle-même, vous pouvez simplement supprimer la dequeue de la fonction et l'appeler depuis une autre fonction . Pour obtenir la longueur de la file d'attente, cet exemple serait:

dopostqueue.queue().length
8
Kim Jensen

J'ai trouvé les solutions ci-dessus assez compliquées, de plus, je devais modifier la demande juste avant l'envoi (pour mettre à jour un nouveau jeton de données).

Alors je mets celui-ci ensemble. Source: https://Gist.github.com/2470554

/* 

Allows for ajax requests to be run synchronously in a queue

Usage::

var queue = new $.AjaxQueue();

queue.add({
  url: 'url',
  complete: function() {
    console.log('ajax completed');
  },
  _run: function(req) {
    //special pre-processor to alter the request just before it is finally executed in the queue
    req.url = 'changed_url'
  }
});

*/

$.AjaxQueue = function() {
  this.reqs = [];
  this.requesting = false;
};
$.AjaxQueue.prototype = {
  add: function(req) {
    this.reqs.Push(req);
    this.next();
  },
  next: function() {
    if (this.reqs.length == 0)
      return;

    if (this.requesting == true)
      return;

    var req = this.reqs.splice(0, 1)[0];
    var complete = req.complete;
    var self = this;
    if (req._run)
      req._run(req);
    req.complete = function() {
      if (complete)
        complete.apply(this, arguments);
      self.requesting = false;
      self.next();
    }

    this.requesting = true;
    $.ajax(req);
  }
};
7
Guy Bedford

Je devais le faire pour un nombre inconnu d'appels ajax. La réponse a été de pousser chacun dans un tableau puis d'utiliser:

$.when.apply($, arrayOfDeferreds).done(function () {
    alert("All done");
});
7
David Woakes

vous pouvez étendre jQuery:

(function($) {
  // 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);

puis utilisez-le comme:

$.ajaxQueue({
    url: 'doThisFirst.php',
    async: true,
    success: function (data) {
        //success handler
    },
    error: function (jqXHR,textStatus,errorThrown) {
        //error Handler
    }       
});
$.ajaxQueue({
    url: 'doThisSecond.php',
    async: true,
    success: function (data) {
        //success handler
    },
    error: function (jqXHR,textStatus,errorThrown) {
        //error Handler
    }       
});

bien sûr, vous pouvez utiliser n'importe quelle autre option de $ .ajax comme type, data, contentType, DataType puisque nous étendons $ .ajax

5
Suing

Une autre version de la réponse de jAndy, sans minuterie.

var ajaxManager = {
    requests: [],
    addReq: function(opt) {
        this.requests.Push(opt);

        if (this.requests.length == 1) {
            this.run();
        }
    },
    removeReq: function(opt) {
        if($.inArray(opt, requests) > -1)
            this.requests.splice($.inArray(opt, requests), 1);
    },
    run: function() {
        // original complete callback
        oricomplete = this.requests[0].complete;

        // override complete callback
        var ajxmgr = this;
        ajxmgr.requests[0].complete = function() {
             if (typeof oricomplete === 'function')
                oricomplete();

             ajxmgr.requests.shift();
             if (ajxmgr.requests.length > 0) {
                ajxmgr.run();
             }
        };

        $.ajax(this.requests[0]);
    },
    stop: function() {
        this.requests = [];
    },
}

Utiliser:

$(function() {
    $("a.button").click(function(){
       ajaxManager.addReq({
           type: 'POST',
           url: 'whatever.html',
           data: params,
           success: function(data){
              // do stuff
           }
       });
    });
});
3
Jeaf Gilbert

Le site learn.jquery.com ayez un bon exemple aussi :

// 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 invoke the next event in the queue
    ajaxOpts.complete = function() {
      // Invoke the original complete if it was there
      if (oldComplete) {
        oldComplete.apply(this, arguments);
      }

      // Run the next query in the queue
      next();
    };

    // Run the query
    $.ajax(ajaxOpts);
  });
};

// Get each item we want to copy
$("#items li").each(function(idx) {
  // Queue up an ajax request
  $.ajaxQueue({
    url: "/ajax_html_echo/",
    data: {
      html: "[" + idx + "] " + $(this).html()
    },
    type: "POST",
    success: function(data) {
      // Write to #output
      $("#output").append($("<li>", {
        html: data
      }));
    }
  });
});
2
Epoc

juste un autre exemple d'un coureur de file d'attente à plusieurs threads que j'ai écrit pour nodejs. Vous pouvez l’adapter à jquery ou angular. Les promesses sont légèrement différentes dans chaque API. J'ai utilisé ce modèle pour extraire tous les éléments de grandes listes dans SharePoint en créant plusieurs requêtes pour extraire toutes les données et en autorisant six à la fois, afin d'éviter les limites de limitation imposées par le serveur.

/*
    Job Queue Runner (works with nodejs promises): Add functions that return a promise, set the number of allowed simultaneous threads, and then run
    (*) May need adaptation if used with jquery or angular promises

    Usage:
        var sourcesQueue = new QueueRunner('SourcesQueue');
        sourcesQueue.maxThreads = 1;
        childSources.forEach(function(source) {
            sourcesQueue.addJob(function() { 
                // Job function - perform work on source
            });
        }
        sourcesQueue.run().then(function(){
            // Queue complete...
        });
*/
var QueueRunner = (function () {
    function QueueRunner(id) {
        this.maxThreads = 1; // Number of allowed simultaneous threads
        this.jobQueue = [];
        this.threadCount = 0;
        this.jobQueueConsumer = null;
        this.jobsStarted = 0;
        if(typeof(id) !== 'undefined') {
            this.id = id;
        }
        else {
            this.id = 'QueueRunner';
        }
    }    
    QueueRunner.prototype.run = function () {
        var instance = this;        
        return new Promise(function(resolve, reject) {
            instance.jobQueueConsumer = setInterval(function() {
                if(instance.threadCount < instance.maxThreads && instance.jobQueue.length > 0) {
                    instance.threadCount++;
                    instance.jobsStarted++;
                    // Remove the next job from the queue (index zero) and run it
                    var job = instance.jobQueue.splice(0, 1)[0];
                    logger.info(instance.id + ': Start job ' + instance.jobsStarted + ' of ' + (instance.jobQueue.length + instance.jobsStarted));
                    job().then(function(){
                        instance.threadCount--;
                    }, function(){
                        instance.threadCount--;
                    });
                }
                if(instance.threadCount < 1 && instance.jobQueue.length < 1) {
                    clearInterval(instance.jobQueueConsumer);
                    logger.info(instance.id + ': All jobs done.');
                    resolve();
                }
            }, 20);
        });     
    };
    QueueRunner.prototype.addJob = function (func) {
        this.jobQueue.Push(func);
    };
    return QueueRunner;
}());
0
Shane

Je devais aussi le faire avec une solution que j'avais et j'ai trouvé que je pouvais le faire de cette façon:

//A variable for making sure to wait for multiple clicks before emptying.
var waitingTimeout; 

$("a.button").click(function(){
   $(this).doAjax(params);
   clearTimeout(waitingTimeout);
   waitingTimeout = setTimeout(function(){noMoreClicks();},1000);
});

// method
doAjax:function(params){ 

   $(document).queue("myQueueName", function(next){
     $.ajax({
       type: 'POST',
       url: 'whatever.html',
       data: params,
       contentType: "application/json; charset=utf-8",
       dataType: "json",
       success: function(data){
         doStuff;
         next();
       },
       failure: function(data){
         next();
       },
       error: function(data){
         next();
       }
     });
   });

}

function noMoreClicks(){
    $(document).dequeue("myQueueName");
}

en utilisant le callback next() transmis dans la fonction de file d'attente, vous pouvez retirer la file d'attente de l'opération suivante. Ainsi, en plaçant le suivant dans les gestionnaires pour ajax, les appels ajax sont effectivement asynchrones pour le navigateur et le rendu ou le fil Paint du navigateur, mais vous les rendez synchrones ou sérialisés entre eux. 

Voici un exemple très basique. Dans l'exemple violon. Cliquez une fois sur le bouton et attendez une seconde. Vous verrez que le délai d'attente se déclenche et qu'une seule opération se produit. Cliquez ensuite sur le bouton aussi vite que vous le pouvez (ou plus rapidement d’une seconde) et vous verrez que toutes les fois que vous cliquez sur le bouton, les opérations sont mises en file d’attente. C’est seulement après avoir attendu une seconde que la page est affichée et qu’elles se L'autre. 

La beauté de ceci est que si la file d'attente est déjà en train de se vider, toutes les opérations que vous ajoutez à celle-ci pendant le processus de vidage sont placées à la fin et ensuite simplement traitées le moment venu.

0
Pow-Ian

En utilisant un framework qui fournit un support observable tel que knockout.js , vous pouvez implémenter une file d 'observation qui, lorsqu'elle est poussée sur, mettra en file d' appel et qu'une équipe procédera au traitement.

Une mise en œuvre knock-out ressemblerait à ceci:

var ajaxQueueMax = 5;
self.ajaxQueue = ko.observableArray();
self.ajaxQueueRunning = ko.observable(0);

ko.computed(function () {
  if (self.ajaxQueue().length > 0 && self.ajaxQueueRunning() < ajaxQueueMax) {
    var next = self.ajaxQueue.shift();
    self.ajaxQueueRunning(self.ajaxQueueRunning() + 1);
    $.ajax(next).always(function () {
      self.ajaxQueueRunning(self.ajaxQueueRunning() - 1);
    });
  }
});

Observez que nous tirons parti des observables nous indiquant quand nous devrions envoyer une autre demande ajax. Cette méthode peut être appliquée sous une forme plus généralisée.

Par exemple, imaginons que vous ayez un mappage masqué qui récupère de nombreuses entrées, mais que vous ayez besoin d'appeler un autre service par élément pour l'enrichir, par exemple, définissez une valeur.

self.widgets = ko.observableArray();

ko.computed(function () {
  var mapping = {
    create: function (options) {
      var res = ko.mapping.fromJS(options.data);
      res.count = ko.observable();

      // widget enrichment.
      self.ajaxQueue.Push({
        dataType: "json",
        url: "/api/widgets/" + options.data.id + "/clicks",
        success: function (data) {
          res.count(data);
        }
      });
      return res;
    }
  };

  // Initial request for widgets
  $.getJSON("/api/widgets", function (data) {
    ko.mapping.fromJS(data, mapping, self.widgets);
  });
});
0
Brett Ryan

Voici ma solution, que j'utilise pour générer une file d'attente de requêtes pour certains Browsergame . Si quelque chose se passe, je stoppe cette file d'attente et termine le travail avec une dernière requête ou un nettoyage spécial.

var get_array = ["first", "second", "third"];

var worker = $("<div />"); // to line up requests in queue
$.queuedAjax = function(args){  // add up requests for me       
    worker.queue(
        function(next){
            $.ajax(args).always(next);            
        }
    );
  };

$.queuedSomething = function(){ // add up something special for me
    worker.queue(
        function(next){
            //worker.clearQueue();
            //worker = $("<div />"); //cleanup for next .each
            //maybe another .each           
        }
    );
  };

$.each( get_array , function( key , value ) {
  $.queuedAjax({
    type: 'GET',
    url: '/some.php?get='+value,
    dataType: 'text',
    success: function(sourcecode){

        if (sourcecode.match(/stop your requests, idiot!/)) {   
            worker.clearQueue().queue($.queuedSomething);
            alert(' the server told me to stop. i stopped all but not the last ´$.queuedSomething()´ ');
        }

    }
  });           
}); 
$.queuedSomething();
0
Nibbels