jQuery callback dla wielu wywołań ajax

136

Chcę wykonać trzy wywołania Ajax w przypadku kliknięcia. Każde wywołanie Ajax wykonuje odrębną operację i zwraca dane potrzebne do końcowego wywołania zwrotnego. Same połączenia nie są od siebie zależne, wszystkie mogą być odbierane w tym samym czasie, jednak chciałbym mieć ostatnie oddzwonienie, gdy wszystkie trzy zostaną zakończone.

$('#button').click(function() {
    fun1();
    fun2();
    fun3();
//now do something else when the requests have done their 'success' callbacks.
});

var fun1= (function() {
    $.ajax({/*code*/});
});
var fun2 = (function() {
    $.ajax({/*code*/});
});
var fun3 = (function() {
    $.ajax({/*code*/});
});
MisterIsaak
źródło

Odpowiedzi:

114

Oto obiekt wywołania zwrotnego, który napisałem, w którym możesz ustawić pojedyncze wywołanie zwrotne tak, aby uruchamiało się po zakończeniu wszystkich lub pozwolić każdemu z nich mieć własne wywołanie zwrotne i uruchomić je wszystkie po zakończeniu:

OGŁOSZENIE

Od jQuery 1.5+ możesz używać metody odroczonej, jak opisano w innej odpowiedzi:

  $.when($.ajax(), [...]).then(function(results){},[...]);

Przykład odroczonego tutaj

dla jQuery <1.5 zadziała poniższe działanie lub jeśli chcesz, aby wywołania ajax były uruchamiane w nieznanych momentach, jak pokazano tutaj z dwoma przyciskami: uruchamiane po kliknięciu obu przycisków

[stosowanie]

dla pojedynczego wywołania zwrotnego po zakończeniu: Przykład roboczy

// initialize here
var requestCallback = new MyRequestsCompleted({
    numRequest: 3,
    singleCallback: function(){
        alert( "I'm the callback");
    }
});

//usage in request
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});

każdy ma swoje własne wywołanie zwrotne po ukończeniu: Przykład roboczy

//initialize 
var requestCallback = new MyRequestsCompleted({
    numRequest: 3
});

//usage in request
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the first callback');
        });
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the second callback');
        });
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the third callback');
        });
    }
});

[Kod]

var MyRequestsCompleted = (function() {
    var numRequestToComplete, requestsCompleted, callBacks, singleCallBack;

    return function(options) {
        if (!options) options = {};

        numRequestToComplete = options.numRequest || 0;
        requestsCompleted = options.requestsCompleted || 0;
        callBacks = [];
        var fireCallbacks = function() {
            alert("we're all complete");
            for (var i = 0; i < callBacks.length; i++) callBacks[i]();
        };
        if (options.singleCallback) callBacks.push(options.singleCallback);

        this.addCallbackToQueue = function(isComplete, callback) {
            if (isComplete) requestsCompleted++;
            if (callback) callBacks.push(callback);
            if (requestsCompleted == numRequestToComplete) fireCallbacks();
        };
        this.requestComplete = function(isComplete) {
            if (isComplete) requestsCompleted++;
            if (requestsCompleted == numRequestToComplete) fireCallbacks();
        };
        this.setCallback = function(callback) {
            callBacks.push(callBack);
        };
    };
})();
subhaze
źródło
1
Dzięki za świetny przykład kodu! Myślę, że uda mi się przedrzeć przez resztę. Dzięki jeszcze raz!
MisterIsaak
Nie ma sprawy, cieszę się, że pomogło! Dało się to trochę ponieść: P wciąż mam na to kilka pomysłów. Planuję zaktualizować go do miejsca, w którym nie musisz określać liczby żądań podczas inicjalizacji.
podhaze
Świetnie, mam nadzieję, że tak! Bardzo ciekawe, proponuję wrzucić opcję dodania dodatkowego oddzwonienia. Zrobiłem i działa idealnie do tego, co chcę robić. Dzięki jeszcze raz!
MisterIsaak
Zrobiłem, ale zapomniałem dodać to do przypadku użycia: P użyj, setCallbackaby dodać więcej do kolejki. Chociaż teraz, kiedy o tym myślę ... Powinienem to nazwać addCallbacklub coś w tym rodzaju ... a nie tylko ustawić, ponieważ dodaje do kolejki
podhaze
Czy singleCallbackzmienna w drugim wierszu jest niepotrzebna? A może coś mi brakuje?
nimcap
161

Wygląda na to, że masz na to kilka odpowiedzi, jednak myślę, że jest tu coś, o czym warto wspomnieć, co znacznie uprości twój kod. jQuery wprowadził $.whenw wersji 1.5. To wygląda jak:

$.when($.ajax(...), $.ajax(...)).then(function (resp1, resp2) {
    //this callback will be fired once all ajax calls have finished.
});

Nie widziałem tego tutaj, mam nadzieję, że to pomoże.

Greg
źródło
7
Dzięki za odpowiedź, to zdecydowanie droga dla mnie. używa jQuery, jest zwarty, krótki i wyrazisty.
nimcap
3
Odpowiedź Subhaze jest świetna, ale tak naprawdę jest to właściwa odpowiedź.
Molomby,
10
Zgadzam się, że jest to dobre rozwiązanie, gdy masz jQuery 1.5+, ale w momencie odpowiedzi odroczona funkcjonalność nie była dostępna. :(
subhaze
13

Sam nie widząc potrzeby jakiegokolwiek obiektu malarky. Simple ma zmienną, która jest liczbą całkowitą. Gdy zaczynasz żądanie, zwiększ liczbę. Kiedy skończysz, zmniejsz go. Kiedy jest zero, nie ma żadnych żądań w toku, więc gotowe.

$('#button').click(function() {
    var inProgress = 0;

    function handleBefore() {
        inProgress++;
    };

    function handleComplete() {
        if (!--inProgress) {
            // do what's in here when all requests have completed.
        }
    };

    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
});
Matt
źródło
Jest to bardzo proste i działa doskonale ... Czy mógłbyś lepiej wyjaśnić, co się dzieje, gdy (--inProgress)? Powinien kontrolować, czy zmienna inProgress == 0 i odjąć do niej jedną jednostkę, ale nie mam zwartej składni ...
Pitto
1
@Pitto: Ups ... właściwie jest tam błąd logiczny i powinien być if (!--inProgress). Prawidłowa wersja jest analogiczna do inProgress = inProgress - 1; if (inProgress == 0) { /* do stuff */ }. Forma zwarta łączy operator dekrementacji przedrostka ( --) i operator negacji ( !), aby obliczyć wartość „prawda” (i tym samym wykonać ifblok), jeśli inProgresswynikiem dekrementacji jest inProgress == 0wartość „fałsz” (i dlatego nic nie robi).
Matt
Po prostu wspaniały! Dziękuję za poświęcony czas i cierpliwość.
Pitto
@Pitto: Humm .. czy możesz podać link do kodu, którego używasz? U mnie to działa .
Matt
11

Warto zauważyć, że ponieważ $.whenoczekuje, że wszystkie żądania AJAX są sekwencyjnymi argumentami (a nie tablicą), które są często $.whenużywane z .apply()takimi:

// Save all requests in an array of jqXHR objects
var requests = arrayOfThings.map(function(thing) {
    return $.ajax({
        method: 'GET',
        url: 'thing/' + thing.id
    });
});

$.when.apply(this, requests).then(function(resp1, resp2/*, ... */) {
    // Each argument is an array with the following structure: [ data, statusText, jqXHR ]
    var responseArgsArray = Array.prototype.slice.call(this, arguments);

});

Korzystając ze składni Spread , możesz teraz napisać następujący kod:

$.when(...requests).then((...responses) => {
    // do something with responses
})

Dzieje się tak, ponieważ $.whenakceptuje takie argumenty

$.when(ajaxRequest1, ajaxRequest2, ajaxRequest3);

A nie tak:

$.when([ajaxRequest1, ajaxRequest2, ajaxRequest3]);
Cory Danielson
źródło
Świetna odpowiedź. Jest teraz wiele obiecujących bibliotek, większość z nich ma allmetodę lub coś podobnego, ale podoba mi się koncepcja mapy. Ładny.
Greg
3
Tak, prawie zawsze używam $., Gdy nakładam na niego tablicę żądań i nie zauważyłem tego w rozwiązaniu tutaj, więc po prostu dodałem go, aby mieć tutaj dobry przykład.
Cory Danielson,
@CoryDanielson My resp1, resp2 to liczby zmienne zależne od rozmiaru tablicy przekazanej w apply (). Czy jest sposób, aby to zadziałało?
insanely_sin
Tak - możesz odwołać się do tablicy argumentów funkcji za pomocą argumentszmiennej. Jeśli używasz nowszych funkcji JS, możesz je również przenieść do zmiennej (...args) => {}.
Cory Danielson
4

Podoba mi się pomysł hvgotcodes. Proponuję dodać ogólny moduł zwiększający, który porównuje kompletny numer z wymaganym numerem, a następnie uruchamia ostateczne wywołanie zwrotne. Można to wbudować w końcowe wywołanie zwrotne.

var sync = {
 callbacksToComplete = 3,
 callbacksCompleted = 0,
 addCallbackInstance = function(){
  this.callbacksCompleted++;
  if(callbacksCompleted == callbacksToComplete) {
   doFinalCallBack();
  }
 }
};

[Zmieniono, aby odzwierciedlić aktualizacje nazwiska].

Adolph Trudeau
źródło
^ Zgoda, pomogło mi lepiej zrozumieć, czego naprawdę potrzebuję.
MisterIsaak
3

EDYCJA - prawdopodobnie najlepszą opcją byłoby utworzenie punktu końcowego usługi, który wykonuje wszystko, co robią trzy żądania. W ten sposób wystarczy wykonać jedno żądanie, a wszystkie dane znajdują się tam, gdzie są potrzebne w odpowiedzi. Jeśli zauważysz, że wykonujesz te same 3 żądania w kółko, prawdopodobnie będziesz chciał iść tą drogą. Często dobrą decyzją projektową jest skonfigurowanie usługi fasadowej na serwerze, która łączy razem powszechnie używane mniejsze akcje serwera. Tylko pomysł.


Jednym ze sposobów byłoby utworzenie obiektu „sync” w module obsługi kliknięć przed wywołaniami AJAX. Coś jak

var sync = {
   count: 0
}

Synchronizacja zostanie automatycznie powiązana z zakresem udanych wywołań (zamknięcie). W programie obsługi sukcesu zwiększasz liczbę, a jeśli wynosi 3, możesz wywołać drugą funkcję.

Alternatywnie możesz zrobić coś takiego

var sync = {
   success1Complete: false,
   ...
   success3Complete: false,
}

gdy każdy sukces zostanie wykonany, zmieni wartość w synchronizacji na true. Przed kontynuowaniem musisz sprawdzić synchronizację, aby upewnić się, że wszystkie trzy są prawdziwe.

Zwróć uwagę na przypadek, w którym jeden z twoich xhrs nie zwraca sukcesu - musisz to uwzględnić.

Jeszcze inną opcją byłoby zawsze wywołanie ostatniej funkcji w programach obsługujących sukces i udostępnienie jej opcji synchronizacji w celu określenia, czy faktycznie cokolwiek zrobić. Musisz jednak upewnić się, że synchronizacja znajduje się w zakresie tej funkcji.

hvgotcodes
źródło
Twoja pierwsza sugestia bardziej mi się podoba z kilku powodów. Każde żądanie ma inny cel, dlatego z tego powodu chciałbym je rozdzielić. Również żądania są w rzeczywistości w funkcjach, ponieważ używam ich gdzie indziej, więc starałem się zachować modułowy projekt, jeśli to możliwe. Dzięki za pomoc!
MisterIsaak
@Jisaak, tak, umieszczenie na serwerze metody radzenia sobie z tym może nie mieć dla ciebie sensu. Ale dopuszczalne jest ustawienie fasady na serwerze. Mówisz o modułowości, tworząc jedną metodę, która obsługuje wszystkie trzy rzeczy, jest modularna.
hvgotcodes
1

Znalazłem łatwiejszy sposób na zrobienie tego bez konieczności stosowania dodatkowych metod porządkujących kolejkę.

JS

$.ajax({
  type: 'POST',
  url: 'ajax1.php',
  data:{
    id: 1,
    cb:'method1'//declaration of callback method of ajax1.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method1'
  eval(cb+"(JSON.stringify(data));");//here we calling method1 and pass all data
  }
});


$.ajax({
  type: 'POST',
  url: 'ajax2.php',
  data:{
    id: 2,
    cb:'method2'//declaration of callback method of ajax2.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method2'
  eval(cb+"(JSON.stringify(data));");//here we calling method2 and pass all data
  }
});


//the callback methods
function method1(data){
//here we have our data from ajax1.php
alert("method1 called with data="+data);
//doing stuff we would only do in method1
//..
}

function method2(data){
//here we have our data from ajax2.php
alert("method2 called with data="+data);
//doing stuff we would only do in method2
//..
}

PHP (ajax1.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method1'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax1"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>

PHP (ajax2.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method2'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax2"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>
Medicalbird
źródło
0

Z odpowiedzi na tej stronie mam kilka dobrych wskazówek. Dostosowałem go trochę do swojego użytku i pomyślałem, że mogę się nim podzielić.

// lets say we have 2 ajax functions that needs to be "synchronized". 
// In other words, we want to know when both are completed.
function foo1(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo1');
            callback();               
        }
    });
}

function foo2(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo2');
            callback();
        }
    });
}

// here is my simplified solution
ajaxSynchronizer = function() {
    var funcs = [];
    var funcsCompleted = 0;
    var callback;

    this.add = function(f) {
        funcs.push(f);
    }

    this.synchronizer = function() {
        funcsCompleted++;
        if (funcsCompleted == funcs.length) {
            callback.call(this);
        }
    }

    this.callWhenFinished = function(cb) {
        callback = cb;
        for (var i = 0; i < funcs.length; i++) {
            funcs[i].call(this, this.synchronizer);
        }
    }
}

// this is the function that is called when both ajax calls are completed.
afterFunction = function() {
    alert('All done!');
}

// this is how you set it up
var synchronizer = new ajaxSynchronizer();
synchronizer.add(foo1);
synchronizer.add(foo2);
synchronizer.callWhenFinished(afterFunction);

Są tu pewne ograniczenia, ale w moim przypadku było dobrze. Odkryłem również, że dla bardziej zaawansowanych rzeczy jest też wtyczka AOP (dla jQuery), która może być przydatna: http://code.google.com/p/jquery-aop/

PålOliver
źródło
0

Natknąłem się dziś na ten problem i to była moja naiwna próba przed obejrzeniem zaakceptowanej odpowiedzi.

<script>
    function main() {
        var a, b, c
        var one = function() {
            if ( a != undefined  && b != undefined && c != undefined ) {
                alert("Ok")
            } else {
                alert( "¬¬ ")
            }
        }

        fakeAjaxCall( function() {
            a = "two"
            one()
        } )
        fakeAjaxCall( function() {
            b = "three"
            one()
        } )
        fakeAjaxCall( function() {
            c = "four"
            one()
        } )
    }
    function fakeAjaxCall( a ) {
        a()
    }
    main()
</script>
OscarRyz
źródło
0

To nie jest jquery (i wygląda na to, że jquery ma działające rozwiązanie), ale po prostu inna opcja ....

Miałem podobne problemy podczas intensywnej pracy z usługami internetowymi SharePoint - często trzeba pobierać dane z wielu źródeł, aby wygenerować dane wejściowe dla pojedynczego procesu.

Aby go rozwiązać, osadziłem tego rodzaju funkcjonalność w mojej bibliotece abstrakcji AJAX. Możesz łatwo zdefiniować żądanie, które po zakończeniu wyzwoli zestaw programów obsługi. Jednak każde żądanie można zdefiniować z wieloma wywołaniami HTTP. Oto komponent (i szczegółowa dokumentacja):

DPAJAX w DepressedPress.com

Ten prosty przykład tworzy jedno żądanie z trzema wywołaniami, a następnie przekazuje te informacje w kolejności wywołań do pojedynczego modułu obsługi:

    // The handler function 
function AddUp(Nums) { alert(Nums[1] + Nums[2] + Nums[3]) }; 

    // Create the pool 
myPool = DP_AJAX.createPool(); 

    // Create the request 
myRequest = DP_AJAX.createRequest(AddUp); 

    // Add the calls to the request 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [5,10]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [4,6]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [7,13]); 

    // Add the request to the pool 
myPool.addRequest(myRequest); 

Zauważ, że w przeciwieństwie do wielu innych rozwiązań (w tym uważam, że rozwiązanie „when” w jquery) pod warunkiem, że ta metoda nie wymusza pojedynczego wątku wykonywanych wywołań - każde z nich będzie nadal działać tak szybko (lub tak wolno), jak pozwala na to środowisko ale pojedynczy program obsługi zostanie wywołany tylko wtedy, gdy wszystkie zostaną ukończone. Obsługuje również ustawianie wartości limitu czasu i ponawianie prób, jeśli usługa jest trochę niestabilna.

Uważam, że jest niesamowicie przydatny (i niezwykle łatwy do zrozumienia z punktu widzenia kodu). Koniec z łańcuchem, koniec z liczeniem połączeń i oszczędzaniem wyjścia. Po prostu „ustaw i zapomnij”.

Jim Davis
źródło
0

Ok, to jest stare, ale pozwól mi wnieść moje rozwiązanie :)

function sync( callback ){
    syncCount--;
    if ( syncCount < 1 ) callback();
}
function allFinished(){ .............. }

window.syncCount = 2;

$.ajax({
    url: 'url',
    success: function(data) {
        sync( allFinished );
    }
});
someFunctionWithCallback( function(){ sync( allFinished ); } )

Działa również z funkcjami, które mają wywołanie zwrotne. Ustawiasz syncCount i wywołujesz funkcję sync (...) w wywołaniu zwrotnym każdej akcji.

sic
źródło
-2
$.ajax({type:'POST', url:'www.naver.com', dataType:'text', async:false,
    complete:function(xhr, textStatus){},
    error:function(xhr, textStatus){},
    success:function( data ){
        $.ajax({type:'POST', 
            ....
            ....
            success:function(data){
                $.ajax({type:'POST',
                    ....
                    ....
            }
        }
    });

Przepraszam, ale nie mogę wyjaśnić, co robię, bo jestem Koreańczykiem, który nie mówi nawet słowa po angielsku. ale myślę, że możesz to łatwo zrozumieć.

nicecue
źródło
1
Nie należy używać Ajax w ten sposób. Piekło oddzwonienia!
traeper
-2
async   : false,

Domyślnie wszystkie żądania są wysyłane asynchronicznie (tzn. Domyślnie jest to ustawione na true). Jeśli potrzebujesz synchronicznych żądań, ustaw tę opcję na false. Żądania i dataType: "jsonp"żądania między domenami nie obsługują operacji synchronicznych. Należy pamiętać, że żądania synchroniczne mogą tymczasowo blokować przeglądarkę, wyłączając wszelkie działania, gdy żądanie jest aktywne. Począwszy od jQuery 1.8 , używanie async: falsez jqXHR ( $.Deferred) jest przestarzałe; musisz użyć opcji wywołania zwrotnego sukcesu / błędu / zakończenia zamiast odpowiednich metod obiektu jqXHR, takich jak jqXHR.done()lub przestarzały jqXHR.success().

Esteves Klein
źródło
1
To nie jest odpowiednie rozwiązanie. Nigdy nie powinieneś wysyłać synchronicznych żądań Ajax.
user128216