Richiamata jQuery per più chiamate Ajax


132

Voglio fare tre chiamate Ajax in un evento click. Ogni chiamata ajax esegue un'operazione distinta e restituisce i dati necessari per una richiamata finale. Le chiamate stesse non dipendono l'una dall'altra, possono andare tutte contemporaneamente, tuttavia vorrei una richiamata finale quando tutte e tre sono complete.

$('#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*/});
});


Risposte:


112

Ecco un oggetto di callback che ho scritto in cui è possibile impostare un singolo callback in modo che si attivi una volta completato o lasciare che ognuno abbia il proprio callback e licenzi tutti una volta completato:

AVVISO

Da jQuery 1.5+ è possibile utilizzare il metodo differito come descritto in un'altra risposta:

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

Esempio di differito qui

per jQuery <1.5 funzionerà quanto segue o se è necessario che le chiamate ajax vengano attivate in momenti sconosciuti, come mostrato qui con due pulsanti: attivato dopo aver fatto clic su entrambi i pulsanti

[Uso]

per callback singolo una volta completato: esempio funzionante

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

ognuno con il proprio callback quando completo: Esempio di lavoro

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

[Il codice]

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

1
Grazie per l'ottimo esempio di codice! Penso che sarò in grado di inciampare nel resto. Grazie ancora!
MisterIsaak,

Nessun problema, felice che abbia aiutato! Sono stato un po 'portato via con esso: P ha ancora qualche idea in più per questo. Ho intenzione di aggiornarlo dove non è necessario specificare un numero di richieste al momento dell'inizializzazione.
subhaze

Bello, spero che tu lo faccia! Molto interessante, suggerirei di aggiungere l'opzione per aggiungere un callback aggiuntivo. L'ho fatto e funziona perfettamente per quello che voglio fare. Grazie ancora!
MisterIsaak,

L'ho fatto ma ho dimenticato di aggiungerlo al caso d'uso: P usare setCallbackper aggiungere altro alla coda. Anche se ora che ci penso ... dovrei chiamarlo addCallbacko qualcosa del genere ... e non solo impostare poiché si sta aggiungendo alla coda
subhaze

La singleCallbackvariabile alla 2a riga non è necessaria? Oppure mi sfugge qualcosa?
nimcap,

158

Sembra che tu abbia alcune risposte a questo, tuttavia penso che ci sia qualcosa che vale la pena menzionare qui che semplificherà notevolmente il tuo codice. jQuery ha introdotto la versione $.when1.5. Sembra:

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

Non l'ho visto menzionato qui, spero che sia d'aiuto.


6
Grazie per la risposta, questa è sicuramente la strada da percorrere per me. utilizza jQuery, è compatto, corto ed espressivo.
nimcap

2
La risposta di Subhaze è ottima ma davvero, questa è quella corretta.
Molomby,

9
Sono d'accordo che questa è una buona soluzione quando hai jQuery 1.5+ ma al momento della risposta la funzionalità differita non era disponibile. :(
subhaze

13

Non vedendo la necessità di alcun oggetto malevolmente da solo. Semplice ha una variabile che è un numero intero. Quando si avvia una richiesta, aumentare il numero. Quando si completa, diminuirlo. Quando è zero, non ci sono richieste in corso, quindi il gioco è fatto.

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

Questo è molto semplice e funziona perfettamente ... Ti dispiacerebbe spiegare un po 'meglio cosa succede in (--inProgress)? Dovrebbe controllare se la variabile inProgress == 0 e sottrarre un'unità ad essa ma non ottengo la sintassi compatta ...
Pitto

1
@Pitto: Oops ... in realtà, c'è un errore logico lì, e dovrebbe esserlo if (!--inProgress). La versione corretta è analoga a inProgress = inProgress - 1; if (inProgress == 0) { /* do stuff */ }. La forma compatta combina l' operatore di decremento-- del prefisso ( ) e l'operatore di negazione ( !) per valutare "vero" (e quindi eseguire il ifblocco), se il decremento inProgressrisulta inProgress == 0, e valuta "falso" (e quindi non fa nulla) altrimenti.
Matt,

Semplicemente fantastico! Grazie per il tuo tempo e la tua pazienza.
Pitto,

@Pitto: Humm .. puoi collegarti al codice che stai usando? Si lavora per me .
Matt,

10

Vale la pena notare che poiché si $.whenaspetta che tutte le richieste ajax siano argomenti sequenziali (non una matrice), si vedrà comunemente $.whenusato in questo .apply()modo:

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

});

Questo perché $.whenaccetta argomenti come questo

$.when(ajaxRequest1, ajaxRequest2, ajaxRequest3);

E non così:

$.when([ajaxRequest1, ajaxRequest2, ajaxRequest3]);

Ottima risposta Ora ci sono molte buone librerie promettenti, molte di queste hanno un allmetodo o qualcosa di simile, ma mi piace il concetto di mappa. Bello.
Greg

2
Sì, quasi sempre uso $ .quando applicando una serie di richieste su di esso, e non l'ho individuato in una soluzione qui, quindi l'ho appena aggiunto per avere un buon esempio qui.
Cory Danielson,

4

Mi piace l'idea di hvgotcodes. Il mio consiglio è di aggiungere un incrementatore generico che confronta il numero completo con il numero necessario e quindi esegue il callback finale. Questo potrebbe essere integrato nel callback finale.

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

[Modificato per riflettere gli aggiornamenti dei nomi.]


^ D'accordo, mi ha aiutato a capire meglio di cosa avevo effettivamente bisogno.
MisterIsaak,

3

MODIFICA: forse l'opzione migliore sarebbe quella di creare un endpoint del servizio che faccia tutto ciò che fanno le tre richieste. In questo modo devi solo fare una richiesta e tutti i dati sono dove ti servono per essere nella risposta. Se scopri che stai ripetendo le stesse 3 richieste, probabilmente vorrai seguire questa strada. Spesso è una buona decisione di progettazione impostare un servizio di facciata sul server che raggruppa insieme azioni di server più piccole comunemente utilizzate. Solo un'idea


un modo per farlo sarebbe quello di creare un oggetto 'sync' nel gestore dei clic prima che le chiamate ajax. Qualcosa di simile a

var sync = {
   count: 0
}

La sincronizzazione sarà automaticamente legata alla portata delle chiamate riuscite (chiusura). Nel gestore del successo, si incrementa il conteggio e se è 3 è possibile chiamare l'altra funzione.

In alternativa, potresti fare qualcosa del genere

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

quando viene eseguito ogni successo, cambia il valore nella sincronizzazione in vero. Dovresti controllare la sincronizzazione per assicurarti che tutti e tre siano veri prima di procedere.

Nota il caso in cui uno dei tuoi xhrs non restituisce successo - devi renderlo conto.

Un'altra opzione sarebbe quella di chiamare sempre la funzione finale nei gestori di successo e di avere accesso all'opzione di sincronizzazione per determinare se fare effettivamente qualcosa. Tuttavia, è necessario assicurarsi che la sincronizzazione rientri nell'ambito di tale funzione.


Mi piace di più il tuo primo suggerimento per un paio di motivi. Ogni richiesta ha uno scopo distinto, quindi mi piacerebbe tenerli separati per questo motivo. Anche le richieste sono effettivamente in una funzione perché le uso altrove, quindi stavo cercando di mantenere un design modulare, se possibile. Grazie per l'aiuto!
MisterIsaak,

@Jisaak, sì, mettere un metodo sul server per affrontare questo potrebbe non avere senso per te. Ma è accettabile installare una facciata sul server. Parli di modularità, la creazione di un metodo che gestisce tutte e tre le cose è modulare.
hvgotcodes,


0

Ho ricevuto alcuni buoni suggerimenti dalle risposte in questa pagina. L'ho adattato un po 'per il mio uso e ho pensato di poterlo condividere.

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

Ci sono alcune limitazioni qui, ma per il mio caso era ok. Ho anche scoperto che per cose più avanzate c'è anche un plug-in AOP (per jQuery) che potrebbe essere utile: http://code.google.com/p/jquery-aop/


0

Oggi ho riscontrato questo problema, e questo era il mio ingenuo tentativo prima di guardare la risposta accettata.

<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>

0

Non è jquery (e sembra che jquery abbia una soluzione praticabile) ma solo come un'altra opzione ...

Ho avuto problemi simili lavorando pesantemente con i servizi Web di SharePoint: spesso è necessario estrarre dati da più origini per generare input per un singolo processo.

Per risolverlo ho incorporato questo tipo di funzionalità nella mia libreria di astrazione AJAX. È possibile definire facilmente una richiesta che attiverà una serie di gestori al termine. Tuttavia, ogni richiesta può essere definita con più chiamate http. Ecco il componente (e documentazione dettagliata):

DPAJAX su DepressedPress.com

Questo semplice esempio crea una richiesta con tre chiamate e quindi passa tali informazioni, nell'ordine di chiamata, a un singolo gestore:

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

Si noti che a differenza di molte altre soluzioni (tra cui, credo, la soluzione "quando" in jquery) purché questo metodo non imponga il threading singolo delle chiamate effettuate, ognuna verrà comunque eseguita più rapidamente (o lentamente) come l'ambiente lo consente ma il gestore singolo verrà chiamato solo quando tutti saranno completi. Supporta inoltre l'impostazione dei valori di timeout e i tentativi di tentativo se il servizio è un po 'debole.

L'ho trovato follemente utile (e incredibilmente semplice da capire dal punto di vista del codice). Niente più concatenamento, niente più conteggi delle chiamate e salvataggio dell'output. Basta "impostarlo e dimenticarlo".


0

Ok, questo è vecchio ma per favore lasciami contribuire con la mia soluzione :)

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

Funziona anche con funzioni che hanno un callback. Impostate syncCount e chiamate la funzione sync (...) nel callback di ogni azione.


0

Ho trovato un modo più semplice per farlo senza la necessità di metodi extra che organizzano una coda.

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

-1
async   : false,

Per impostazione predefinita, tutte le richieste vengono inviate in modo asincrono (ovvero, per impostazione predefinita, questo è impostato su true). Se sono necessarie richieste sincrone, impostare questa opzione su false. Le richieste tra domini e dataType: "jsonp"richieste non supportano il funzionamento sincrono. Si noti che le richieste sincrone possono bloccare temporaneamente il browser, disabilitando qualsiasi azione mentre la richiesta è attiva. A partire da jQuery 1.8 , l'uso di async: falsecon jqXHR ( $.Deferred) è obsoleto; è necessario utilizzare le opzioni esito positivo / errore / callback completo anziché i metodi corrispondenti dell'oggetto jqXHR come jqXHR.done()o deprecato jqXHR.success().


Questa non è una soluzione adeguata. Non si dovrebbe mai fare una richiesta Ajax sincrona.
user128216,

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

Mi dispiace, ma non riesco a spiegare cosa devo dire perché sono un coreano che non sa nemmeno parlare una parola in inglese. ma penso che tu possa capirlo facilmente.


Non utilizzare Ajax in questo modo. Callback Hell ..!
traditore il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.