jQuery-Rückruf für mehrere Ajax-Aufrufe

132

Ich möchte drei Ajax-Aufrufe in einem Klickereignis tätigen. Jeder Ajax-Aufruf führt eine bestimmte Operation aus und gibt Daten zurück, die für einen endgültigen Rückruf benötigt werden. Die Anrufe selbst sind nicht voneinander abhängig, sie können alle gleichzeitig ablaufen, ich hätte jedoch gerne einen letzten Rückruf, wenn alle drei abgeschlossen sind.

$('#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
quelle

Antworten:

112

Hier ist ein Rückrufobjekt, das ich geschrieben habe, in dem Sie entweder einen einzelnen Rückruf so einstellen können, dass er ausgelöst wird, sobald alle abgeschlossen sind, oder dass jeder seinen eigenen Rückruf hat und alle einmal abgeschlossen wird:

BEACHTEN

Seit jQuery 1.5+ können Sie die verzögerte Methode wie in einer anderen Antwort beschrieben verwenden:

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

Beispiel hier aufgeschoben

Für jQuery <1.5 funktioniert Folgendes oder wenn Sie Ihre Ajax-Anrufe zu unbekannten Zeiten abfeuern müssen, wie hier gezeigt, mit zwei Schaltflächen: Wird ausgelöst, nachdem beide Schaltflächen angeklickt wurden

[Verwendung]

für einen einzelnen Rückruf nach Abschluss: Arbeitsbeispiel

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

Jeder hat seinen eigenen Rückruf, wenn alle abgeschlossen sind: Arbeitsbeispiel

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

[Der Code]

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
quelle
1
Danke für das tolle Codebeispiel! Ich denke, ich werde mich durch den Rest stolpern können. Danke noch einmal!
MisterIsaak
Kein Problem, ich bin froh, dass es geholfen hat! Ich wurde irgendwie mitgerissen: P habe noch ein paar Ideen dafür. Ich plane, es so zu aktualisieren, dass Sie bei der Initialisierung keine Anzahl von Anforderungen angeben müssen.
Subhaze
Schön, ich hoffe du tust es! Sehr interessant, ich würde vorschlagen, die Option zu aktivieren, um einen zusätzlichen Rückruf hinzuzufügen. Ich habe es getan und es funktioniert perfekt für das, was ich tun möchte. Danke noch einmal!
MisterIsaak
Ich habe aber vergessen, es dem Anwendungsfall setCallbackhinzuzufügen : P verwenden , um der Warteschlange mehr hinzuzufügen. Obwohl jetzt, wo ich darüber nachdenke ... ich sollte es addCallbackoder so etwas nennen ... und nicht nur einstellen, da es der Warteschlange
hinzugefügt wird
Ist die singleCallbackVariable in der 2. Zeile nicht erforderlich? Oder fehlt mir etwas?
Nimcap
158

Sieht so aus, als hätten Sie einige Antworten darauf, aber ich denke, hier gibt es etwas Erwähnenswertes, das Ihren Code erheblich vereinfacht. jQuery führte das $.whenin v1.5 ein. Es sieht aus wie:

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

Ich habe es hier nicht erwähnt gesehen, hoffe es hilft.

Greg
quelle
6
Vielen Dank für die Antwort, dies ist definitiv der richtige Weg für mich. Es verwendet jQuery, ist kompakt, kurz und ausdrucksstark.
Nimcap
2
Subhazes Antwort ist großartig, aber wirklich, das ist die richtige.
Molomby
9
Ich bin damit einverstanden, dass dies eine gute Lösung ist, wenn Sie über jQuery 1.5+ verfügen, aber zum Zeitpunkt der Antwort keine verzögerte Funktionalität verfügbar war. :(
Subhaze
13

Ich sehe selbst nicht die Notwendigkeit eines Objekts. Einfach haben eine Variable, die eine ganze Zahl ist. Wenn Sie eine Anfrage starten, erhöhen Sie die Anzahl. Wenn man fertig ist, dekrementiere es. Wenn es Null ist, werden keine Anforderungen ausgeführt, sodass Sie fertig sind.

$('#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
quelle
Dies ist sehr einfach und funktioniert perfekt ... Würde es Ihnen etwas ausmachen, etwas besser zu erklären, was in if (--inProgress) passiert? Es sollte steuern, ob die Variable inProgress == 0 ist und eine Einheit davon subtrahieren, aber ich bekomme nicht die kompakte Syntax ...
Pitto
1
@Pitto: Ups ... eigentlich gibt es dort einen logischen Fehler, und das sollte auch so sein if (!--inProgress). Die richtige Version ist analog zu inProgress = inProgress - 1; if (inProgress == 0) { /* do stuff */ }. Die kompakte Form kombiniert den Präfix-Dekrementierungsoperator ( --) und den Negationsoperator ( !), um "wahr" zu bewerten (und daher den ifBlock auszuführen ), wenn das Dekrementieren zu inProgressergibt inProgress == 0, und wertet sonst "falsch" aus (und tut daher nichts).
Matt
Einfach wunderbar! Vielen Dank für Ihre Zeit und Geduld.
Pitto
@Pitto: Humm .. kannst du auf den Code verlinken, den du verwendest? Es funktioniert bei mir .
Matt
10

Es ist erwähnenswert, dass, da $.whenalle Ajax-Anforderungen als sequentielle Argumente (kein Array) erwartet werden, die Sie häufig mit folgender Adresse $.whenverwenden .apply():

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

});

Dies liegt daran, dass $.whensolche Argumente akzeptiert werden

$.when(ajaxRequest1, ajaxRequest2, ajaxRequest3);

Und nicht so:

$.when([ajaxRequest1, ajaxRequest2, ajaxRequest3]);
Cory Danielson
quelle
Tolle Resonanz. Es gibt jetzt viele vielversprechende Bibliotheken, die meisten haben eine allMethode oder ähnliches, aber ich mag das Kartenkonzept. Nett.
Greg
2
Ja, ich verwende fast immer $ .when, indem ich eine Reihe von Anforderungen darauf anwende, und habe dies in einer Lösung hier nicht erkannt, also habe ich es einfach hinzugefügt, um hier ein gutes Beispiel zu haben.
Cory Danielson
4

Ich mag die Idee von hvgotcodes. Mein Vorschlag ist, einen generischen Inkrementierer hinzuzufügen, der die vollständige Nummer mit der benötigten Nummer vergleicht und dann den letzten Rückruf ausführt. Dies könnte in den endgültigen Rückruf eingebaut werden.

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

[Bearbeitet, um Namensaktualisierungen widerzuspiegeln.]

Adolph Trudeau
quelle
^ Einverstanden, half ein besseres Verständnis für das, was ich tatsächlich brauchte.
MisterIsaak
3

BEARBEITEN - Vielleicht ist die beste Option, einen Service-Endpunkt zu erstellen, der alles tut, was die drei Anforderungen tun. Auf diese Weise müssen Sie nur eine Anfrage ausführen, und alle Daten befinden sich dort, wo Sie sie in der Antwort benötigen. Wenn Sie feststellen, dass Sie immer wieder dieselben 3 Anfragen ausführen, möchten Sie wahrscheinlich diesen Weg gehen. Es ist oft eine gute Entwurfsentscheidung, einen Fassadendienst auf dem Server einzurichten, der häufig verwendete kleinere Serveraktionen zusammenfasst. Nur eine Idee.


Eine Möglichkeit besteht darin, vor dem Aufrufen von Ajax ein Synchronisierungsobjekt in Ihrem Klick-Handler zu erstellen. Etwas wie

var sync = {
   count: 0
}

Die Synchronisierung wird automatisch an den Umfang der Erfolgsaufrufe gebunden (Abschluss). Im Success-Handler erhöhen Sie die Anzahl, und wenn es 3 ist, können Sie die andere Funktion aufrufen.

Alternativ könnten Sie so etwas tun

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

Wenn jeder Erfolg ausgeführt wird, ändert sich der Wert in der Synchronisierung in true. Sie müssten die Synchronisierung überprüfen, um sicherzustellen, dass alle drei zutreffen, bevor Sie fortfahren.

Beachten Sie den Fall, dass einer Ihrer xhrs keinen Erfolg zurückgibt - Sie müssen dies berücksichtigen.

Eine weitere Option wäre, immer die letzte Funktion in Ihren Erfolgshandlern aufzurufen und sie auf die Synchronisierungsoption zugreifen zu lassen, um zu bestimmen, ob tatsächlich etwas getan werden soll. Sie müssten jedoch sicherstellen, dass die Synchronisierung im Umfang dieser Funktion liegt.

hvgotcodes
quelle
Ich mag Ihren ersten Vorschlag aus mehreren Gründen mehr. Jede Anfrage hat einen bestimmten Zweck, daher möchte ich sie aus diesem Grund getrennt halten. Außerdem befinden sich die Anforderungen tatsächlich in einer Funktion, da ich sie an anderer Stelle verwende. Daher habe ich versucht, wenn möglich einen modularen Aufbau beizubehalten. Danke für Ihre Hilfe!
MisterIsaak
@Jisaak, ja, eine Methode auf den Server zu setzen, um damit umzugehen, macht für Sie möglicherweise keinen Sinn. Es ist jedoch akzeptabel, eine Fassade auf dem Server einzurichten. Sie sprechen von Modularität. Die Erstellung einer Methode, die alle drei Dinge behandelt, ist modular.
Hvgotcodes
0

Die Antworten auf dieser Seite haben mir einige gute Hinweise gegeben. Ich habe es ein wenig für meinen Gebrauch angepasst und dachte, ich könnte es teilen.

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

Hier gibt es einige Einschränkungen, aber für meinen Fall war es in Ordnung. Ich fand auch, dass es für fortgeschrittenere Sachen auch ein AOP-Plugin (für jQuery) gibt, das nützlich sein könnte: http://code.google.com/p/jquery-aop/

PålOliver
quelle
0

Ich bin heute auf dieses Problem gestoßen, und dies war mein naiver Versuch, bevor ich die akzeptierte Antwort sah.

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

Es ist nicht jquery (und es scheint, dass jquery eine praktikable Lösung hat), sondern nur als eine weitere Option ....

Ich hatte ähnliche Probleme bei der Arbeit mit SharePoint-Webdiensten. Oft müssen Sie Daten aus mehreren Quellen abrufen, um Eingaben für einen einzelnen Prozess zu generieren.

Um dies zu lösen, habe ich diese Art von Funktionalität in meine AJAX-Abstraktionsbibliothek eingebettet. Sie können einfach eine Anforderung definieren, die nach Abschluss eine Reihe von Handlern auslöst. Jede Anforderung kann jedoch mit mehreren http-Aufrufen definiert werden. Hier ist die Komponente (und detaillierte Dokumentation):

DPAJAX bei DepressedPress.com

In diesem einfachen Beispiel wird eine Anforderung mit drei Aufrufen erstellt und diese Informationen in der Anrufreihenfolge an einen einzelnen Handler übergeben:

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

Beachten Sie, dass im Gegensatz zu vielen anderen Lösungen (einschließlich der "Wann" -Lösung in jquery) diese Methode kein einzelnes Threading der getätigten Aufrufe erzwingt - jede wird weiterhin so schnell (oder so langsam) ausgeführt, wie es die Umgebung zulässt Der einzelne Handler wird jedoch nur aufgerufen, wenn alle vollständig sind. Es unterstützt auch das Festlegen von Zeitlimitwerten und Wiederholungsversuchen, wenn Ihr Dienst ein wenig unzuverlässig ist.

Ich fand es wahnsinnig nützlich (und aus Code-Sicht unglaublich einfach zu verstehen). Keine Verkettung mehr, kein Zählen von Anrufen und Speichern der Ausgabe mehr. Einfach "einstellen und vergessen".

Jim Davis
quelle
0

Ok, das ist alt, aber bitte lassen Sie mich meine Lösung beitragen :)

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

Es funktioniert auch mit Funktionen, die einen Rückruf haben. Sie setzen den syncCount und rufen die Funktion sync (...) im Rückruf jeder Aktion auf.

sic
quelle
0

Ich habe einen einfacheren Weg gefunden, ohne zusätzliche Methoden zum Anordnen einer Warteschlange zu benötigen.

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
quelle
-1
async   : false,

Standardmäßig werden alle Anforderungen asynchron gesendet (dh dies ist standardmäßig auf true gesetzt). Wenn Sie synchrone Anforderungen benötigen, setzen Sie diese Option auf false. Domänenübergreifende Anforderungen und dataType: "jsonp"Anforderungen unterstützen den synchronen Betrieb nicht. Beachten Sie, dass synchrone Anforderungen den Browser vorübergehend sperren und alle Aktionen deaktivieren können, während die Anforderung aktiv ist. Ab jQuery 1.8 ist die Verwendung von async: falsemit jqXHR ( $.Deferred) veraltet. Sie müssen die Rückrufoptionen Erfolg / Fehler / Vollständig anstelle der entsprechenden Methoden des jqXHR- Objekts wie jqXHR.done()oder veraltet verwenden jqXHR.success().

Esteves Klein
quelle
Dies ist keine adäquate Lösung. Sie sollten niemals eine synchrone Ajax-Anfrage stellen.
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',
                    ....
                    ....
            }
        }
    });

Es tut mir leid, aber ich kann nicht erklären, was ich getan habe, weil ich ein Koreaner bin, der nicht einmal ein Wort auf Englisch sprechen kann. aber ich denke, Sie können es leicht verstehen.

nicecue
quelle
Sollte Ajax nicht auf diese Weise verwenden. Rückruf Hölle ..!
Traeper