Wie kann ich auf eine Reihe von asynchronen Rückruffunktionen warten?

94

Ich habe Code, der in Javascript ungefähr so ​​aussieht:

forloop {
    //async call, returns an array to its callback
}

Nachdem ALLE diese asynchronen Aufrufe abgeschlossen sind, möchte ich die min über alle Arrays berechnen.

Wie kann ich auf alle warten?

Meine einzige Idee im Moment ist es, ein Array von Booleschen Werten namens done zu haben und done [i] in der i-ten Rückruffunktion auf true zu setzen und dann while (nicht alle sind erledigt) {} zu sagen

edit: Ich nehme an, eine mögliche, aber hässliche Lösung wäre, das erledigte Array in jedem Rückruf zu bearbeiten und dann eine Methode aufzurufen, wenn alle anderen erledigten von jedem Rückruf festgelegt sind. Daher ruft der letzte abgeschlossene Rückruf die fortlaufende Methode auf.

Danke im Voraus.

Codierer sind Menschen
quelle
1
Meinen Sie bei asynchronem Warten auf den Abschluss einer Ajax-Anfrage?
Peter Aron Zentai
6
Beachten Sie, while (not all are done) { }würde nicht funktionieren. Während Sie warten, kann keiner Ihrer Rückrufe ausgeführt werden.
CHao
Ja. Ich warte darauf, dass ein asynchroner Aufruf einer externen API zurückgegeben wird, damit die Rückrufmethoden ausgelöst werden. Yeah cHao, das habe ich gemerkt, weshalb ich hier um Hilfe bitte: D
codersarepeople
Sie können dies versuchen: github.com/caolan/async Sehr schöne Reihe von asynchronen Dienstprogrammfunktionen.
Paul Greyson

Antworten:

191

Sie waren mit Ihrem Code nicht sehr spezifisch, daher werde ich ein Szenario erstellen. Angenommen, Sie haben 10 Ajax-Anrufe und möchten die Ergebnisse dieser 10 Ajax-Anrufe sammeln. Wenn alle abgeschlossen sind, möchten Sie etwas unternehmen. Sie können dies so tun, indem Sie die Daten in einem Array sammeln und nachverfolgen, wann der letzte fertig ist:

Manueller Zähler

var ajaxCallsRemaining = 10;
var returnedData = [];

for (var i = 0; i < 10; i++) {
    doAjax(whatever, function(response) {
        // success handler from the ajax call

        // save response
        returnedData.push(response);

        // see if we're done with the last ajax call
        --ajaxCallsRemaining;
        if (ajaxCallsRemaining <= 0) {
            // all data is here now
            // look through the returnedData and do whatever processing 
            // you want on it right here
        }
    });
}

Hinweis: Die Fehlerbehandlung ist hier wichtig (wird nicht angezeigt, da sie sich darauf bezieht, wie Sie Ihre Ajax-Anrufe tätigen). Sie sollten sich überlegen, wie Sie mit dem Fall umgehen sollen, wenn ein Ajax-Aufruf nie abgeschlossen wird, entweder mit einem Fehler oder wenn er für eine lange Zeit stecken bleibt oder nach einer langen Zeit eine Zeitüberschreitung aufweist.


jQuery verspricht

Zu meiner Antwort im Jahr 2014 hinzufügen. Heutzutage werden Versprechen häufig verwendet, um diese Art von Problem zu lösen, da jQuery $.ajax()bereits ein Versprechen zurückgibt und $.when()Sie darüber informiert, wenn eine Gruppe von Versprechen alle gelöst sind, und die Rückgabeergebnisse für Sie sammelt:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push($.ajax(...));
}
$.when.apply($, promises).then(function() {
    // returned data is in arguments[0][0], arguments[1][0], ... arguments[9][0]
    // you can process it here
}, function() {
    // error occurred
});

ES6-Standardversprechen

Wie in der Antwort von kba angegeben : Wenn Sie eine Umgebung mit integrierten nativen Versprechungen haben (moderner Browser oder node.js oder Verwendung von babeljs transpile oder Verwendung einer Versprechen-Polyfüllung), können Sie ES6-spezifizierte Versprechungen verwenden. In dieser Tabelle finden Sie Informationen zur Browserunterstützung. Versprechen werden in nahezu allen aktuellen Browsern mit Ausnahme des IE unterstützt.

Wenn doAjax()ein Versprechen zurückgegeben wird, können Sie dies tun:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

Wenn Sie eine asynchrone Operation ohne Versprechen in eine Operation verwandeln müssen, die ein Versprechen zurückgibt, können Sie sie wie folgt "versprechen":

function doAjax(...) {
    return new Promise(function(resolve, reject) {
        someAsyncOperation(..., function(err, result) {
            if (err) return reject(err);
            resolve(result);
        });
    });
}

Und dann verwenden Sie das obige Muster:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

Bluebird verspricht

Wenn Sie eine funktionsreichere Bibliothek wie die Bluebird-Versprechensbibliothek verwenden , sind einige zusätzliche Funktionen integriert, um dies zu vereinfachen:

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // all ajax results here
 }, function(err) {
     // some error here
 });
jfriend00
quelle
4
@kba - Ich hätte diese Antwort nicht genau als veraltet bezeichnet, da alle Techniken noch anwendbar sind, insbesondere wenn Sie bereits jQuery für Ajax verwenden. Aber ich habe es auf verschiedene Weise aktualisiert, um native Versprechen aufzunehmen.
jfriend00
In diesen Tagen gibt es eine viel sauberere Lösung, die nicht einmal jquery benötigt. Ich mache es mit FetchAPI und Promises
philx_x
@philx_x - Was machst du mit IE- und Safari-Unterstützung?
jfriend00
@ jfriend00 github hat eine Polyfüllung github.com/github/fetch erstellt . Oder ich bin mir nicht sicher, ob Babel Fetch noch unterstützt. babeljs.io
philx_x
@philx_x - Ich dachte schon. Sie benötigen heutzutage eine Polyfill-Bibliothek, um Fetch verwenden zu können. Entlastet Ihren Kommentar zur Vermeidung einer Ajax-Bibliothek ein wenig. Fetch ist nett, aber es ist Jahre entfernt, es ohne Polyfill verwenden zu können. Es ist noch nicht einmal in der neuesten Version aller Browser. Tun Sie, es ändert nichts an meiner Antwort. Ich hatte eine doAjax(), die ein Versprechen als eine der Optionen zurückgibt. Das Gleiche wie fetch().
jfriend00
17

Einchecken ab 2015: Wir haben jetzt native Versprechen in den neuesten Browsern (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 und Android Browser 4.4.4 und iOS Safari 8.4, jedoch nicht in Internet Explorer, Opera Mini und älteren Versionen von Android).

Wenn wir 10 asynchrone Aktionen ausführen und benachrichtigt werden möchten, wenn alle abgeschlossen sind, können wir die native Aktion Promise.allohne externe Bibliotheken verwenden:

function asyncAction(i) {
    return new Promise(function(resolve, reject) {
        var result = calculateResult();
        if (result.hasError()) {
            return reject(result.error);
        }
        return resolve(result);
    });
}

var promises = [];
for (var i=0; i < 10; i++) {
    promises.push(asyncAction(i));
}

Promise.all(promises).then(function AcceptHandler(results) {
    handleResults(results),
}, function ErrorHandler(error) {
    handleError(error);
});
kba
quelle
2
Promises.all()sollte sein Promise.all().
jfriend00
1
Ihre Antwort muss sich auch darauf beziehen, welche Browser Sie verwenden können, Promise.all()in denen keine aktuellen IE-Versionen enthalten sind.
jfriend00
10

Sie können jQuery verwenden latentes mit der entlangen Objekt , wenn Methode.

deferredArray = [];
forloop {
    deferred = new $.Deferred();
    ajaxCall(function() {
      deferred.resolve();
    }
    deferredArray.push(deferred);
}

$.when(deferredArray, function() {
  //this code is called after all the ajax calls are done
});
Paul
quelle
7
Die Frage wurde nicht markiert, jQuerywas normalerweise bedeutet, dass das OP keine jQuery-Antwort wollte.
jfriend00
8
@ jfriend00 Ich wollte das Rad nicht neu erfinden, als es bereits in jQuery erstellt wurde
Paul
4
@Paul also lieber neu erfinden als das Rad, das 40kb Müll enthält, um etwas Einfaches zu tun (aufgeschoben)
Raynos
2
Aber nicht jeder kann oder will jQuery verwenden, und der Brauch hier auf SO ist, dass Sie angeben, ob Sie Ihre Frage mit jQuery markieren oder nicht.
jfriend00
4
Der Aufruf von $ .when in diesem Beispiel ist falsch. Um auf eine Reihe von zurückgestellten / Versprechen zu warten, müssen Sie $ .when.apply ($, Versprechen) .then (function () {/ * do stuff * /}) verwenden.
Danw
9

Sie können es folgendermaßen emulieren:

  countDownLatch = {
     count: 0,
     check: function() {
         this.count--;
         if (this.count == 0) this.calculate();
     },
     calculate: function() {...}
  };

dann führt jeder asynchrone Aufruf Folgendes aus:

countDownLatch.count++;

Während Sie bei jedem asynchronen Rückruf am Ende der Methode diese Zeile hinzufügen:

countDownLatch.check();

Mit anderen Worten, Sie emulieren eine Countdown-Latch-Funktion.

Eugene Retunsky
quelle
In 99% aller Anwendungsfälle ist ein Versprechen der richtige Weg, aber ich mag diese Antwort, weil sie eine Methode zum Verwalten von Async-Code in Situationen darstellt, in denen eine Versprechen-Polyfüllung größer ist als die JS, die sie verwendet!
Sukima
6

Dies ist meiner Meinung nach der ordentlichste Weg.

Promise.all

FetchAPI

(Aus irgendeinem Grund funktioniert Array.map nicht in .then-Funktionen für mich. Sie können jedoch .forEach und [] .concat () oder ähnliches verwenden.)

Promise.all([
  fetch('/user/4'),
  fetch('/user/5'),
  fetch('/user/6'),
  fetch('/user/7'),
  fetch('/user/8')
]).then(responses => {
  return responses.map(response => {response.json()})
}).then((values) => {
  console.log(values);
})
philx_x
quelle
1
Ich denke das muss sein return responses.map(response => { return response.json(); }), oder return responses.map(response => response.json()).
1

Verwenden Sie eine Kontrollflussbibliothek wie after

after.map(array, function (value, done) {
    // do something async
    setTimeout(function () {
        // do something with the value
        done(null, value * 2)
    }, 10)
}, function (err, mappedArray) {
    // all done, continue here
    console.log(mappedArray)
})
Raynos
quelle