Pregunta ¿Cómo puedo esperar al conjunto de funciones de devolución de llamada asincrónica?


Tengo un código que se ve así en javascript:

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

Después de que se realicen TODAS esas llamadas asincrónicas, quiero calcular el mínimo en todas las matrices.

¿Cómo puedo esperar a todos?

Mi única idea en este momento es tener una matriz de booleanos llamada done, y establecer done [i] en true en la i-ésima función de devolución de llamada, luego decir while (no todo está hecho) {}

editar: supongo que una solución posible, pero fea, sería editar el conjunto hecho en cada devolución de llamada, luego llamar a un método si todos los demás están configurados desde cada devolución de llamada, por lo que la última devolución de llamada para completar llamará al método continuo.

Gracias por adelantado.


75
2018-04-04 02:14


origen


Respuestas:


No has sido muy específico con tu código, así que voy a inventar un escenario. Supongamos que tiene 10 llamadas ajax y desea acumular los resultados de esas 10 llamadas ajax y luego, cuando se hayan completado, quiere hacer algo. Puedes hacerlo así acumulando los datos en una matriz y haciendo un seguimiento de cuándo ha finalizado la última:

Contador manual

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

Nota: el manejo de errores es importante aquí (no se muestra porque es específico de cómo estás haciendo tus llamadas ajax). Deberá pensar en cómo manejará el caso cuando una llamada ajax nunca se complete, ya sea con un error o se quede atascada durante mucho tiempo o se agote después de un tiempo prolongado.


jQuery Promises

Agregando a mi respuesta en 2014. En estos días, las promesas se utilizan a menudo para resolver este tipo de problema ya que jQuery $.ajax() ya devuelve una promesa y $.when() le informará cuándo se han resuelto un grupo de promesas y recopilará los resultados para usted:

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

Promesas estándar ES6

Como se especifica en la respuesta de kba: si tiene un entorno con promesas nativas incorporadas (navegador moderno o node.js o usando babeljs transpile o usando un pliegue potencial prometedor), entonces puede usar promesas especificadas por ES6. Ver Esta mesa para soporte de navegador Las promesas son compatibles en casi todos los navegadores actuales, excepto IE.

Si doAjax() devuelve una promesa, entonces puedes hacer esto:

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

Si necesita convertir una operación asincrónica no prometedora en una que devuelva una promesa, puede "promisificarla" así:

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

Y, luego usa el patrón de arriba:

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 Promises

Si usa una biblioteca con muchas más funciones, como Biblioteca de promesa Bluebird, entonces tiene algunas funciones adicionales integradas para facilitar esto:

 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



Check in desde 2015: ahora tenemos promesas nativas en navegador más reciente (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 y Android 4.4.4 y iOS Safari 8.4, pero no Internet Explorer, Opera Mini y versiones anteriores de Android).

Si queremos realizar 10 acciones asíncronas y recibir una notificación cuando todas hayan terminado, podemos usar el nativo Promise.all, sin ninguna biblioteca externa:

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



Puedes usar jQuery's Diferido objeto junto con el cuando método.

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



Puedes emularlo así:

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

entonces cada llamada asincrónica hace esto:

countDownLatch.count++;

mientras que en cada llamada de Asynch al final del método agrega esta línea:

countDownLatch.check();

En otras palabras, emula una funcionalidad de conteo regresivo.


7
2018-04-04 02:21



Esta es la forma más clara en mi opinión.

Promise.all

FetchAPI

(Por alguna razón, Array.map no funciona dentro. Entonces funciona para mí. Pero puedes usar un .forEach y [] .concat () o algo similar)

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



Use una biblioteca de flujo de control como 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