Как связать вызовы ajax с помощью jquery - PullRequest
33 голосов
/ 23 декабря 2011

Мне нужно сделать серию N ajax-запросов без блокировки браузера, и я хочу использовать отложенный объект jquery для этого.

Вот упрощенный пример с тремя запросами, но моей программе может потребоваться поставить в очередь более 100 (обратите внимание, что это не точный вариант использования, реальный код должен гарантировать успех шага (N-1) перед тем, как выполнение следующего шага):

$(document).ready(function(){

    var deferred = $.Deferred();

    var countries = ["US", "CA", "MX"];

    $.each(countries, function(index, country){

        deferred.pipe(getData(country));

    });

 });

function getData(country){

    var data = {
        "country": country  
    };


    console.log("Making request for [" + country + "]");

    return $.ajax({
        type: "POST",
        url: "ajax.jsp",
        data: data,
        dataType: "JSON",
        success: function(){
            console.log("Successful request for [" + country + "]");
        }
    });

}

Вот что записывается в консоль (все запросы выполняются параллельно, а время отклика прямо пропорционально размеру данных для каждой страны, как и ожидалось:

Making request for [US]
Making request for [CA]
Making request for [MX]
Successful request for [MX]
Successful request for [CA]
Successful request for [US]

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

Вот желаемый результат:

Making request for [US]
Successful request for [US]
Making request for [CA]
Successful request for [CA]
Making request for [MX]
Successful request for [MX]

Edit:

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

Это то, что я пытаюсь сделать:

when(request[0]).pipe(request[1]).pipe(request[2])... pipe(request[N]);

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

deferred.pipe(request[0]);
deferred.pipe(request[1]);
deferred.pipe(request[2]);

Ответы [ 6 ]

31 голосов
/ 23 декабря 2011

С пользовательским объектом

function DeferredAjax(opts) {
    this.options=opts;
    this.deferred=$.Deferred();
    this.country=opts.country;
}
DeferredAjax.prototype.invoke=function() {
    var self=this, data={country:self.country};
    console.log("Making request for [" + self.country + "]");

    return $.ajax({
        type: "GET",
        url: "wait.php",
        data: data,
        dataType: "JSON",
        success: function(){
            console.log("Successful request for [" + self.country + "]");
            self.deferred.resolve();
        }
    });
};
DeferredAjax.prototype.promise=function() {
    return this.deferred.promise();
};


var countries = ["US", "CA", "MX"], startingpoint = $.Deferred();
startingpoint.resolve();

$.each(countries, function(ix, country) {
    var da = new DeferredAjax({
        country: country
    });
    $.when(startingpoint ).then(function() {
        da.invoke();
    });
    startingpoint= da;
});

Fiddle http://jsfiddle.net/7kuX9/1/

Чтобы быть немного более понятным, последние строки могут быть написаны

c1=new DeferredAjax( {country:"US"} );
c2=new DeferredAjax( {country:"CA"} );
c3=new DeferredAjax( {country:"MX"} );

$.when( c1 ).then( function() {c2.invoke();} );
$.when( c2 ).then( function() {c3.invoke();} );

С трубами

function fireRequest(country) {
        return $.ajax({
            type: "GET",
            url: "wait.php",
            data: {country:country},
            dataType: "JSON",
            success: function(){
                console.log("Successful request for [" + country + "]");
            }
        });
}

var countries=["US","CA","MX"], startingpoint=$.Deferred();
startingpoint.resolve();

$.each(countries,function(ix,country) {
    startingpoint=startingpoint.pipe( function() {
        console.log("Making request for [" + country + "]");
        return fireRequest(country);
    });
});

http://jsfiddle.net/k8aUj/1/

Редактировать: Скрипка, выводящая журнал в окне результатов http://jsfiddle.net/k8aUj/3/

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

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

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

Примечание : по состоянию на jQuery 1.8, deferred.pipe() устарело, deferred.then заменяет его.

5 голосов
/ 02 марта 2013

Примечание: Начиная с jquery 1.8, вы можете использовать .then вместо .pipe. Функция .then теперь возвращает новое обещание, а .pipe устарела, так как больше не нужна. См. спецификации обещаний для получения дополнительной информации об обещаниях и q.js для более чистой библиотеки обещаний JavaScript без зависимости от jquery.

countries.reduce(function(l, r){
  return l.then(function(){return getData(r)});
}, $.Deferred().resolve());

и если вы хотите использовать q.js:

//create a closure for each call
function getCountry(c){return function(){return getData(c)};}
//fire the closures one by one
//note: in Q, when(p1,f1) is the static version of p1.then(f1)
countries.map(getCountry).reduce(Q.when, Q());

Оригинальный ответ:

Еще одна труба; не для слабонервных, но немного компактнее:

countries.reduce(function(l, r){
  return l.pipe(function(){return getData(r)});
}, $.Deferred().resolve());

Сокращение документации , вероятно, лучшее место, чтобы начать понимать, как работает приведенный выше код. По сути, он принимает два аргумента: обратный вызов и начальное значение.

Обратный вызов применяется итеративно ко всем элементам массива, где его первый аргумент передается в результате предыдущей итерации, а второй аргумент является текущим элементом. Хитрость в том, что getData() возвращает отложенное обещание jquery , и канал проверяет, что перед вызовом getData для текущего элемента завершается getData предыдущего элемента.

Второй аргумент $.Deferred().resolve() - это идиома для разрешенного отложенного значения. Он подается на первую итерацию выполнения обратного вызова и обеспечивает немедленный вызов getData для первого элемента.

4 голосов
/ 19 сентября 2012

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

Ваш getData(country) вызывается немедленно из-за того, как вы кодировали свойпараметр трубы.Как вы понимаете, getData() выполняется немедленно, и результат (обещание ajax, но http-запрос начинается немедленно) передается в качестве параметра pipe().Поэтому вместо передачи функции обратного вызова вы передаете объект, что приводит к немедленному разрешению новой отложенной функции канала.

Я думаю, это должно быть

deferred.pipe(function () { return getData(country); });

Теперь этофункция обратного вызова, которая будет вызываться после разрешения родительского канала.Кодирование таким образом поднимет вторую проблему.Ни один из getData () не будет выполняться до тех пор, пока не будет разрешено ведущее время.

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

Итак, я думаю, вам нужно что-то подобное

var countries = ["US", "CA", "MX"];
var deferred = $.Deferred();
var promise = deferred.promise();

$.each(countries, function(index, country) {
    promise = promise.pipe(function () { return getData(country); });
});

deferred.resolve();
4 голосов
/ 23 декабря 2011

Я не совсем уверен, почему вы хотите это сделать, но храните список всех URL-адресов, которые вам нужно запросить, и не запрашивайте следующий, пока не будет вызвана ваша функция success.IE, success будет условно совершать дополнительные звонки на deferred.

3 голосов
/ 03 июля 2013

У меня был успех с очередями jQuery.

$(function(){
    $.each(countries, function(i,country){
      $('body').queue(function() {
        getData(country);
      });
    });
});

var getData = function(country){
  $.ajax({
    url : 'ajax.jsp',
    data : { country : country },
    type : 'post',
    success : function() {                          
      // Que up next ajax call
      $('body').dequeue();
    },
    error : function(){
      $('body').clearQueue();
    }
  });
};
2 голосов
/ 13 февраля 2012

Обновление: deferred.pipe устарела

Это много кода для того, что уже задокументировано в jQuery API. см http://api.jquery.com/deferred.pipe/

Вы можете просто продолжать их обвязывать, пока все 100 не будут сделаны.

Или я написал что-то, чтобы сделать N вызовов и разрешить одну функцию с данными всех выполненных вызовов. Примечание: он возвращает данные, а не объект super XHR. https://gist.github.com/1219564

...