Frage Wie kann ich auf asynchrone Callback-Funktionen warten?


Ich habe Code, der in Javascript so aussieht:

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

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

Wie kann ich auf alle warten?

Meine einzige Idee ist im Moment, eine Reihe von booleschen Werten done zu haben und done [i] in der i-ten Callback-Funktion auf true zu setzen und dann zu sagen while (nicht alle sind fertig) {}

edit: Ich nehme an, eine mögliche, aber hässliche Lösung wäre, das Array done in jedem Callback zu editieren und dann eine Methode aufzurufen, wenn alle anderen done von jedem Callback aus gesetzt werden. Somit ruft der letzte Callback die fortlaufende Methode auf.

Danke im Voraus.


75
2018-04-04 02:14


Ursprung


Antworten:


Du warst nicht sehr spezifisch mit deinem Code, also werde ich ein Szenario erfinden. Nehmen wir an, Sie haben 10 Ajax-Anrufe und Sie möchten die Ergebnisse dieser 10 Ajax-Anrufe ansammeln und dann, wenn alle fertig sind, möchten Sie etwas tun. Sie können dies tun, indem Sie die Daten in einem Array sammeln und verfolgen, 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: Fehlerbehandlung ist hier wichtig (nicht gezeigt, weil es spezifisch ist, wie Sie Ihre Ajax-Anrufe machen). Sie werden darüber nachdenken müssen, wie Sie mit dem Fall umgehen werden, wenn ein Ajax-Aufruf niemals abgeschlossen wird, entweder mit einem Fehler oder er bleibt für eine lange Zeit oder nach einer langen Zeit aus.


jQuery Versprechen

Hinzufügen zu meiner Antwort im Jahr 2014. In diesen Tagen werden Versprechen oft verwendet, um diese Art von Problem seit jQuery zu lösen $.ajax() gibt bereits ein Versprechen und $.when() wird Sie wissen lassen, wenn eine Gruppe von Versprechen alle gelöst ist und die Ergebnisse für Sie sammeln wird:

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 kba's Antwort angegeben: Wenn Sie eine Umgebung mit integrierten nativen Versprechungen (moderner Browser oder node.js oder Verwendung von babeljs transpile oder Verwenden eines Polyfill-Versprechens) haben, können Sie ES6-spezifizierte Versprechungen verwenden. Sehen dieser Tisch für die Browserunterstützung. Versprechen werden in fast allen aktuellen Browsern mit Ausnahme von IE unterstützt.

Ob doAjax() gibt ein Versprechen zurück, dann 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 nicht-versprechen-asynchrone Operation zu einer machen müssen, die eine Zusage zurückgibt, können Sie sie wie folgt "promistifizieren":

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

Und dann benutze 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 Versprechen

Wenn Sie eine funktionsreichere Bibliothek wie z Bluebird-Versprechen-Bibliothek, dann hat es einige zusätzliche Funktionen eingebaut, um das 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
 });

159
2018-04-04 02:19



Einchecken ab 2015: Wir haben jetzt Einheimische Versprechen im letzter Browser (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 und Android Browser 4.4.4 und iOS Safari 8.4, aber nicht Internet Explorer, Opera Mini und ältere Versionen von Android).

Wenn wir 10 asynchrone Aktionen ausführen und benachrichtigt werden möchten, wenn alle fertig sind, können wir das native verwenden Promise.all, ohne externe Bibliotheken:

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

12
2017-11-19 20:29



Sie können jQuery verwenden Aufgeschoben Objekt zusammen mit der wann 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
});

10
2018-04-04 02:19



Du kannst es so emulieren:

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

dann führt jeder async-Aufruf das aus:

countDownLatch.count++;

während Sie in jedem asynch-Aufruf am Ende der Methode diese Zeile hinzufügen:

countDownLatch.check();

Mit anderen Worten, Sie emulieren eine Countdown-Latch-Funktionalität.


7
2018-04-04 02:21



Dies ist meiner Meinung nach der sauberste Weg.

Versprechen.all

FetchAPI

(Aus irgendeinem Grund funktioniert Array.map nicht innerhalb von .then funktioniert für mich. Aber Sie können eine .forEach und [] .concat () oder etwas ä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);
})

3
2018-04-14 19:30



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

1
2018-04-04 02:28