Последовательность запросов AJAX - PullRequest
62 голосов
/ 14 июня 2010

Я считаю, что иногда мне нужно выполнить итерацию некоторой коллекции и сделать ajax-вызов для каждого элемента.Я хочу, чтобы каждый вызов возвращался до перехода к следующему элементу, чтобы я не загружал сервер запросами, что часто приводит к другим проблемам.И я не хочу устанавливать для async значение false и замораживать браузер.

Обычно это включает в себя настройку некоторого контекста итератора, который я выполняю при каждом обратном вызове.Я думаю, что должен быть более простой и понятный способ?

Есть ли у кого-нибудь умный шаблон дизайна для того, как аккуратно обрабатывать коллекцию, делая вызовы ajax для каждого элемента?

Ответы [ 9 ]

110 голосов
/ 14 июня 2010

JQuery 1,5 +

Я разработал плагин $.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, вы можете использовать очередь анимации для пустого объекта, чтобы создать собственную «очередь» для ваших запросов ajax для элементов.

Вы можете даже учесть это в своей собственной $.ajax() замене. Этот плагин $.ajaxQueue() использует стандартную очередь 'fx' для jQuery, которая автоматически запускает первый добавленный элемент, если очередь еще не запущена.

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

Пример использования

Итак, у нас есть <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 демонстрация - 1.4 версия

13 голосов
/ 17 декабря 2013

Быстрое и маленькое решение с использованием отложенных обещаний.Хотя здесь используется jQuery $.Deferred, любой другой должен делать.

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

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

Использование, вызов для создания новых очередей:

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

См. пример параллельное сравнение асинхронных запросов.

3 голосов
/ 14 июня 2010

В идеале сопрограмма с несколькими точками входа, поэтому каждый обратный вызов с сервера может вызывать одну и ту же сопрограмму, должна быть аккуратной. Черт, это должно быть реализовано в Javascript 1.7.

Позвольте мне попробовать использовать замыкание ...

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 голосов
/ 14 июня 2010

Вы можете обернуть всю эту сложность в функцию, чтобы сделать простой вызов, который выглядит следующим образом:

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

Ниже приведен грубый набросок (рабочий пример, кроме вызова 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
     }
  };
2 голосов
/ 09 августа 2017

Я публикую этот ответ, полагая, что в будущем он может помочь другим людям, ищущим несколько простых решений по тому же сценарию.

Теперь это возможно также с помощью встроенной поддержки обещаний, представленной в ES6.Вы можете заключить вызов 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
        });

    });
}

Теперь рекурсивно вызывайте функцию, откуда у вас есть коллекция элементов.

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;
    }
}
2 голосов
/ 08 апреля 2012

Да, в то время как другие ответы будут работать, они много кода и выглядят грязно. Frame.js был разработан для элегантного решения этой ситуации. https://github.com/bishopZ/Frame.js

Например, это приведет к зависанию большинства браузеров:

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

Пока этого не будет:

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

Кроме того, использование 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()
1 голос
/ 19 августа 2018

Я бы предложил немного более сложный подход, который можно использовать в разных случаях.
Я использую его, например, когда мне нужно замедлить последовательность вызовов, когда пользователь печатает в текстовом редакторе.

Но я уверен, что это должно работать и при переборе коллекции. В этом случае он может ставить запросы в очередь и может отправлять один вызов 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++;
    }
}
1 голос
/ 08 января 2016

Вы можете достичь того же, используя 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 голос
/ 14 июня 2010

Я использую http://developer.yahoo.com/yui/3/io/#queue, чтобы получить эту функциональность.

Единственное решение, которое я могу предложить, это, как вы говорите, ведение списка ожидающих вызовов / обратных вызовов.Или вложив следующий вызов в предыдущий обратный вызов, но это выглядит немного грязно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...