Jak mogę czekać na zestaw asynchronicznych funkcji zwrotnych?


95

Mam kod, który wygląda mniej więcej tak w javascript:

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

Po wykonaniu WSZYSTKICH wywołań asynchronicznych chcę obliczyć min dla wszystkich tablic.

Jak mogę czekać na nich wszystkich?

Moim jedynym pomysłem w tej chwili jest mieć tablicę wartości logicznych o nazwie done i ustawić done [i] na true w i-tej funkcji zwrotnej, a następnie powiedzieć while (nie wszystkie są gotowe) {}

edit: Przypuszczam, że jednym możliwym, ale brzydkim rozwiązaniem byłoby edytowanie gotowej tablicy w każdym wywołaniu zwrotnym, a następnie wywołanie metody, jeśli wszystkie inne gotowe są ustawione z każdego wywołania zwrotnego, a zatem ostatnie wywołanie zwrotne do zakończenia wywoła metodę kontynuacji.

Z góry dziękuję.


1
Czy w trybie asynchronicznym masz na myśli czekanie na zakończenie żądania Ajax?
— Peter Aron Zentai

6
Uwaga, while (not all are done) { }nie zadziała. Gdy jesteś zajęty czekaniem, żadne z Twoich oddzwonień nie może działać.
— cHao

Tak. Czekam na wywołanie asynchroniczne do zewnętrznego interfejsu API, aby powrócić, aby uruchomić 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 funkcji narzędziowych asynchronicznych.
— Paul Greyson,

Odpowiedzi:


191

Nie byłeś zbyt szczegółowy w swoim kodzie, więc wymyślę scenariusz. Powiedzmy, że masz 10 wywołań Ajax i chcesz zebrać wyniki z tych 10 wywołań AJAX, a gdy wszystkie się zakończą, chcesz coś zrobić. Możesz to zrobić w ten sposób, gromadząc dane w tablicy i śledząc, kiedy zakończy się ostatnia:

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 jest wyświetlana, ponieważ jest specyficzna dla sposobu wykonywania połączeń Ajax). Będziesz chciał pomyśleć o tym, jak poradzisz sobie z przypadkiem, gdy jedno wywołanie ajax nigdy się nie kończy, albo z błędem, albo utknie na długi czas lub po długim czasie upłynie.


jQuery Promises

Dodając do mojej odpowiedzi w 2014 roku. Obecnie obietnice są często używane do rozwiązywania tego typu problemów, ponieważ jQuery $.ajax()już zwraca obietnicę i $.when()poinformuje Cię, kiedy grupa obietnic zostanie rozwiązana, i zbierze 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 określono w odpowiedzi kba : jeśli masz środowisko z wbudowanymi natywnymi obietnicami (nowoczesna przeglądarka lub node.js lub używając babeljs transpile lub używając wypełnienia obietnicy), możesz użyć obietnic określonych w ES6. Zobacz tę tabelę, aby uzyskać informacje o obsłudze przeglądarek. Obietnice są obsługiwane w prawie wszystkich obecnych przeglądarkach, z wyjątkiem IE.

Jeśli doAjax()zwróci 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ć operację asynchroniczną niezwiązaną z obietnicą w taką, która zwraca obietnicę, możesz ją „obiecać” w następujący sposób:

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

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

Bluebird obiecuje

Jeśli używasz bogatszej w funkcje biblioteki, takiej jak biblioteka obietnic Bluebird , ma ona wbudowane dodatkowe funkcje, które to ułatwiają:

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // all ajax results here
 }, function(err) {
     // some error here
 });

4
@kba - nie nazwałbym tej odpowiedzi przestarzałą, ponieważ wszystkie techniki nadal mają zastosowanie, szczególnie jeśli używasz już jQuery dla Ajax. Ale zaktualizowałem go na kilka sposobów, aby uwzględnić natywne obietnice.
— jfriend00

Obecnie istnieje znacznie czystsze rozwiązanie, które nawet nie wymaga jquery. Robię to z FetchAPI i Promises
— philx_x,

@philx_x - Co robisz w związku z obsługą IE i Safari?
— jfriend00

@ jfriend00 github utworzył polifill github.com/github/fetch . Albo nie jestem jeszcze pewien, czy babel obsługuje pobieranie. babeljs.io
— philx_x

@philx_x - Tak myślałem. Aby korzystać z funkcji pobierania, potrzebujesz obecnie biblioteki polyfill. Wyciąga trochę powietrza z twojego komentarza na temat unikania biblioteki Ajax. Fetch jest fajny, ale minęły lata, zanim będzie można go używać bez polyfill. Nie ma go jeszcze w najnowszej wersji wszystkich przeglądarek. Tak naprawdę to niczego nie zmienia w mojej odpowiedzi. Miałem doAjax()opcję, która zwraca obietnicę jako jedną z opcji. To samo co fetch().
— jfriend00

17

Meldowanie od 2015 roku: mamy teraz natywne obietnice w najnowszej przeglądarce (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 systemu Android).

Jeśli chcemy wykonać 10 akcji asynchronicznych i otrzymać powiadomienie o zakończeniu wszystkich, możemy skorzystać z natywnej 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);
});

2
Promises.all()powinno być Promise.all().
— jfriend00

1
Twoja odpowiedź musi również odnosić się do przeglądarek, z których możesz korzystać, Promise.all()co nie obejmuje aktualnych wersji IE.
— jfriend00

10

Można użyć jQuery odroczony obiektu wraz z , gdy metody.

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

7
Pytanie nie zostało oznaczone, jQueryco zwykle oznacza, że ​​operator operacyjny nie chciał odpowiedzi jQuery.
— jfriend00

8
@ jfriend00 Nie chciałem wymyślać na nowo koła, które zostało już utworzone w jQuery
— Paul

4
@Paul, więc zamiast wymyślić ponownie koło, w tym 40kb śmieci, aby zrobić coś prostego (odroczone)
— Raynos

2
Ale nie każdy może lub chce używać jQuery, a tutaj w SO jest to zwyczaj, że wskazujesz to, czy oznaczasz swoje pytanie jQuery, czy nie.
— jfriend00

4
$ .When call to ten przykład jest niepoprawny. Aby czekać na tablicę odroczonych / obietnic, musisz użyć $ .when.apply ($, promises) .then (function () {/ * do stuff * /}).
— danw

9

Możesz to naśladować w ten sposób:

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

to każde wywołanie asynchroniczne robi to:

countDownLatch.count++;

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

countDownLatch.check();

Innymi słowy, emulujesz funkcję odliczania w dół.


W 99% wszystkich przypadków użycia Promise jest drogą do zrobienia, ale podoba mi się ta odpowiedź, ponieważ ilustruje metodę zarządzania kodem Async w sytuacjach, gdy wypełnienie Promise jest większe niż JS, który go używa!
— Sukima

6

To moim zdaniem najbardziej zgrabny sposób.

Promise.all

FetchAPI

(z jakiegoś powodu Array.map nie działa dla mnie wewnątrz funkcji .then. Ale możesz użyć .forEach i [] .concat () lub czegoś 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);
})

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

1

Użyj biblioteki przepływu sterowania, 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)
})
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.