Jak łączyć połączenia ajax za pomocą jquery

Muszę wykonać serię N żądań ajax bez blokowania przeglądarki i chcę użyć obiektu jQuery deferred do tego celu.

Oto uproszczony przykład z trzema żądaniami, ale mój program może potrzebować kolejki powyżej 100 (zauważ, że nie jest to dokładny przypadek użycia, rzeczywisty kod musi zapewnić powodzenie kroku (N-1) przed wykonaniem następnego kroku):

$(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 + "]");
        }
    });

}

Oto co zostanie zapisane w konsoli (wszystkie żądania są wykonywane równolegle i Czas odpowiedzi jest wprost proporcjonalny do wielkości danych dla każdego kraju zgodnie z oczekiwaniami:

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

Jak mogę sprawić, by obiekt odroczony ustawił je w kolejce? Próbowałem zmienić zrobione do rury, ale uzyskać ten sam wynik.

Oto pożądany rezultat:

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:

Doceniam sugestię użycia tablicy do przechowywania parametrów żądania, ale obiekt odroczony jquery ma możliwość kolejkowania żądań i naprawdę chcę się nauczyć, jak tego używać funkcji do pełnego potencjału.

Właśnie to staram się zrobić:

when(request[0]).pipe(request[1]).pipe(request[2])... pipe(request[N]);
Jednakże, chcę przypisać żądania do rury krok po kroku, aby efektywnie korzystać z każdego przejścia:
deferred.pipe(request[0]);
deferred.pipe(request[1]);
deferred.pipe(request[2]);
Author: George Stocker, 2011-12-23

6 answers

Z własnym obiektem

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/

Aby być bardziej przejrzystym, można by napisać Ostatnie linijki

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

Z rurami

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/

Edit: skrzypek wypisujący log w oknie wynikowym http://jsfiddle.net/k8aUj/3/

Każde wywołanie rury zwraca nową obietnicę, która z kolei jest używana dla następnej rury. Zauważ, że podałem tylko funkcja sccess, podobna funkcja powinna być zapewniona w przypadku awarii.

[[5]}w każdym rozwiązaniu wywołania Ajax są opóźniane do czasu potrzebnego przez zawinięcie ich w funkcję i dla każdego elementu listy tworzona jest nowa obietnica do zbudowania łańcucha.

Wierzę, że niestandardowy obiekt zapewnia łatwiejszy sposób manipulowania łańcuchem, ale rury mogą lepiej pasować do Twoich gustów.

Notatka : od jQuery 1.8, deferred.pipe() jest przestarzały, deferred.then zastępuje go.

 30
Author: nikoshr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-07 11:01:28

Uwaga: od jquery 1.8 możesz używać .then zamiast .pipe. Funkcja .then zwraca teraz nową obietnicę i .pipe jest przestarzała, ponieważ nie jest już potrzebna. Zobacz promises specaby uzyskać więcej informacji na temat promises, oraz q.js aby uzyskać czystszą bibliotekę obietnic javascript bez zależności od jquery.

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

I jeśli lubisz używać 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());

oryginalna odpowiedź:

[[9]}kolejna fajka; nie dla słabego serca, ale trochę więcej zwarta:
countries.reduce(function(l, r){
  return l.pipe(function(){return getData(r)});
}, $.Deferred().resolve());

Reduce documentation jest prawdopodobnie najlepszym miejscem, aby zacząć rozumieć, jak działa powyższy kod. Zasadniczo wymaga dwóch argumentów, wywołania zwrotnego i wartości początkowej.

Wywołanie zwrotne jest stosowane iteracyjnie do wszystkich elementów tablicy, gdzie pierwszy argument jest podawany jako wynik poprzedniej iteracji, a drugi jako bieżący element. Sztuczka polega na tym, że getData() zwraca jQuery deferred promise , a rura upewnia się, że przed wywołaniem getData na bieżącym elemencie zostanie zakończona funkcja getData poprzedniego elementu.

Drugi argument $.Deferred().resolve() jest idiomem dla rozdzielonej wartości odroczonej. Jest on podawany do pierwszej iteracji wywołania zwrotnego i upewnia się, że getData na pierwszym elemencie jest natychmiast wywołana.

 5
Author: topkara,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-15 12:56:52

Nie jestem do końca pewien, dlaczego chcesz to zrobić, ale zachowaj listę wszystkich adresów URL, o które musisz poprosić, i nie żądaj następnego, dopóki nie zostanie wywołana funkcja success. Tj. success warunkowo wykona dodatkowe wywołania do deferred.

 4
Author: ziesemer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-23 06:29:21

Wiem, że jestem na to spóźniony, ale wierzę, że twój oryginalny kod jest w większości w porządku, ale ma dwa (może trzy) problemy.

Twój {[2] } jest wywoływany natychmiast ze względu na sposób zakodowania parametru rury. Sposób, w jaki to masz, getData() jest wykonywany natychmiast, a wynik (obietnica ajax, ale żądanie http rozpoczyna się natychmiast) jest przekazywany jako parametr do pipe(). Więc zamiast przekazać funkcję zwrotną, przekazujesz obiekt-co powoduje, że nowa rura będzie natychmiast rozwiązany.

Myślę, że to musi być

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

Teraz jest to funkcja zwrotna, która zostanie wywołana, gdy zostanie rozwiązany rodzic rury. Kodowanie go w ten sposób spowoduje drugi problem. Żadna z funkcji getData()nie zostanie wykonana, dopóki nie zostanie rozwiązany master deferred.

Potencjalnym trzecim problemem może być to, że skoro wszystkie twoje rury byłyby przymocowane do mistrza, to tak naprawdę nie masz łańcucha i zastanawiam się, czy mógłby je wszystkie wykonać w w każdym razie o tej samej porze. Dokumenty mówią, że wywołania zwrotne są wykonywane w kolejności, ale ponieważ oddzwanianie zwraca obietnicę i działa asynchronicznie, mogą one nadal wykonywać nieco równolegle.

Więc myślę, że potrzebujesz czegoś takiego

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
Author: Jeff Shepler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-15 12:53:25

Update: odroczone.pipe is deprecated

Jest to dużo kodu na coś, co jest już udokumentowane w jQuery API. zobacz http://api.jquery.com/deferred.pipe/

Możesz je po prostu orurować, dopóki wszystkie 100 nie zostaną wykonane.

Lub, napisałem coś, aby wykonać N wywołań, i rozwiązać jedną funkcję z danymi wszystkich połączeń, które zostały wykonane. Notatka: zwraca dane, a nie obiekt super XHR. https://gist.github.com/1219564

 2
Author: Drew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-07 10:51:47

Odniosłem sukces z kolejkami 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
Author: Daniel Bardi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-03 17:32:44