Pytanie Jak mogę poczekać na zestaw asynchronicznych funkcji wywołania zwrotnego?


Mam kod, który wygląda podobnie w javascript:

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

Po wykonaniu WSZYSTKICH tych wywołań asynchronicznych, chcę obliczyć min na wszystkich tablicach.

Jak mogę na nich czekać?

Jedyny mój pomysł w tej chwili polega na tym, że wywoływana jest tablica wywołań funkcji boole i ustawiona [i] na wartość true w funkcji wywołania zwrotnego i, a następnie powiedz (nie wszystkie są zakończone) {}

edit: Przypuszczam, że jednym z możliwych, ale brzydkich rozwiązań, byłoby edytowanie wykonanej tablicy w każdym wywołaniu zwrotnym, a następnie wywołanie metody, jeśli wszystkie inne wykonane są ustawione z każdego wywołania zwrotnego, a zatem ostatnie wywołanie zwrotne do wywołania metody ciągłej.

Z góry dziękuję.


76
2018-04-04 02:14


pochodzenie


Na async masz na myśli czekanie na zakończenie żądania Ajax? - Peter Aron Zentai
Uwaga, while (not all are done) { } nie działa. Podczas gdy jesteś zajęty czekaniem, żaden z twoich callbacków nie może działać. - cHao
Tak. Czekam na wywołanie asynchroniczne z zewnętrznym API, aby powrócić, aby wywołać metody wywołania zwrotnego. Tak cHao, zdałem sobie z tego sprawę, dlatego proszę o pomoc tutaj: D - codersarepeople
Możesz spróbować tego: github.com/caolan/async Bardzo ładny zestaw asynchronicznych funkcji użytkowych. - Paul Greyson


Odpowiedzi:


Nie znasz kodu, więc przygotuję scenariusz. Załóżmy, że masz 10 wywołań ajaxowych i chcesz zebrać wyniki z tych 10 wywołań ajaxowych, a kiedy już wszystkie zostaną zakończone, chcesz coś zrobić. Możesz zrobić to w ten sposób, gromadząc dane w tablicy i śledząc, kiedy ostatni został zakończony:

Licznik ręczny

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

Uwaga: obsługa błędów jest tutaj ważna (nie pokazana, ponieważ jest specyficzna dla sposobu wykonywania wywołań ajaxowych). Będziesz chciał pomyśleć o tym, jak sobie poradzisz z przypadkiem, gdy jedno połączenie ajaxowe nigdy się nie zakończy, albo z błędem, albo utknie przez długi czas, albo po dłuższej przerwie.


Obietnice jQuery

Dodając do mojej odpowiedzi w 2014 roku. W dzisiejszych czasach obietnice są często wykorzystywane do rozwiązania tego typu problemów od czasu jQuery $.ajax() już zwraca obietnicę i $.when() poinformuje Cię, kiedy grupa obietnic zostanie rozwiązana i zbierze dla Ciebie wyniki zwrotu:

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

Standardowe obietnice ES6

Jak podano w odpowiedzi KBA: jeśli masz środowisko z natywnymi wbudowanymi obietnicami (nowoczesna przeglądarka lub node.js lub używanie babeljs transpile lub przy użyciu obiecanej polyfill), możesz użyć obietnic określonych w ES6. Widzieć ten stół do obsługi przeglądarki. Obietnice są obsługiwane w prawie wszystkich obecnych przeglądarkach, z wyjątkiem IE.

Gdyby doAjax() zwraca obietnicę, możesz to zrobić:

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

Jeśli chcesz wykonać nieakceptującą operację asynchroniczną w taki, który zwróci obietnicę, możesz ją "oznaczyć" w ten sposób:

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

A następnie użyj powyższego wzoru:

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

Obietnice Bluebird

Jeśli używasz biblioteki o większej funkcjonalności, takiej jak Biblioteka obietnic Bluebird, to ma wbudowane dodatkowe funkcje, aby było to łatwiejsze:

 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



Rozwiązanie jQuery działa całkiem nieźle! - larrydahooster
@kba - nie nazwałbym tej odpowiedzi jako przestarzałą, ponieważ wszystkie techniki są nadal odpowiednie, szczególnie jeśli używasz już jQuery dla Ajax. Ale zaktualizowałem go na kilka sposobów, aby uwzględnić natywne obietnice. - jfriend00
te dni jest znacznie czystsze rozwiązanie, które nawet nie wymaga jquery. Robię to za pomocą FetchAPI i obietnic - philx_x
@philx_x - Co robisz w obsłudze IE i Safari? - jfriend00
@ jfriend00 github zrobił polyfill github.com/github/fetch. Lub nie jestem pewien, czy babel obsługuje pobieranie jeszcze. babeljs.io - philx_x


Odprawa z 2015: teraz mamy natywne obietnice w najnowsza przeglądarka (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 i Android 4.4.4 i iOS Safari 8.4, ale nie Internet Explorer, Opera Mini i starsze wersje Androida).

Jeśli chcemy wykonać 10 asynchronicznych akcji i otrzymać powiadomienie, gdy wszystkie zostaną zakończone, możemy użyć natywnego Promise.all, bez żadnych zewnętrznych bibliotek:

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



Promises.all() powinno być Promise.all(). - jfriend00
Twoja odpowiedź również musi się odnosić które przeglądarki możesz użyć Promise.all() w którym nie ma aktualnych wersji IE. - jfriend00


Możesz użyć jQuery Odroczony obiekt wraz z gdy metoda.

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



Pytanie nie zostało oznaczone jQuery co zwykle oznacza, że ​​OP nie chciał odpowiedzi jQuery. - jfriend00
@ Jfriend00 Nie chciałem wymyślać koła, gdy był już utworzony w jQuery - Paul
@Paul, a następnie ponownie wymyślić koło, w tym 40kb śmieci, aby zrobić coś prostego (odkłada) - Raynos
Ale nie każdy może lub chce używać jQuery, a niestandardowy tutaj na SO jest to, że wskazujesz, czy oznaczysz swoje pytanie jQuery, czy nie. - jfriend00
Wywołanie $ .when jest tym błędem. Aby czekać na tablicę odroczonych / obietnic, musisz użyć $ .when.apply ($, obietnice) .then (function () {/ * do stuff * /}). - danw


Możesz naśladować to tak:

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

następnie każde wywołanie asynchroniczne wykonuje to:

countDownLatch.count++;

podczas gdy w każdym wywołaniu asynch na końcu metody dodajesz tę linię:

countDownLatch.check();

Innymi słowy, emulujesz funkcję blokowania odliczania.


7
2018-04-04 02:21



W 99% przypadków używasz obietnicy, ale podoba mi się ta odpowiedź, ponieważ ilustruje ona metodę zarządzania kodem asynchronicznym w sytuacjach, w których polifill Obietnica jest większa niż JS, który jej używa! - Sukima


To najmilszy sposób według mnie.

Obiecuj

FetchAPI

(z jakiegoś powodu Array.map nie działa wewnątrz. then funkcje dla mnie, ale możesz użyć .forEach i [] .concat () lub coś podobnego)

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



Myślę, że tak musi być return responses.map(response => { return response.json(); }), lub return responses.map(response => response.json()).


Użyj biblioteki kontrolnej, takiej jak 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