Espere a que se resuelvan todas las promesas

107

Entonces tengo una situación en la que tengo múltiples cadenas de promesas de longitud desconocida. Quiero que se ejecute alguna acción cuando se hayan procesado todas las CADENAS. ¿Es eso siquiera posible? Aquí hay un ejemplo:

app.controller('MainCtrl', function($scope, $q, $timeout) {
    var one = $q.defer();
    var two = $q.defer();
    var three = $q.defer();

    var all = $q.all([one.promise, two.promise, three.promise]);
    all.then(allSuccess);

    function success(data) {
        console.log(data);
        return data + "Chained";
    }

    function allSuccess(){
        console.log("ALL PROMISES RESOLVED")
    }

    one.promise.then(success).then(success);
    two.promise.then(success);
    three.promise.then(success).then(success).then(success);

    $timeout(function () {
        one.resolve("one done");
    }, Math.random() * 1000);

    $timeout(function () {
        two.resolve("two done");
    }, Math.random() * 1000);

    $timeout(function () {
        three.resolve("three done");
    }, Math.random() * 1000);
});

En este ejemplo, configuro una $q.all()para las promesas uno, dos y tres que se resolverán en algún momento aleatorio. Luego agrego promesas en los extremos de uno y tres. Quiero allque se resuelva cuando se hayan resuelto todas las cadenas. Aquí está la salida cuando ejecuto este código:

one done 
one doneChained
two done
three done
ALL PROMISES RESOLVED
three doneChained
three doneChainedChained 

¿Hay alguna forma de esperar a que se resuelvan las cadenas?

jensengar
fuente

Respuestas:

161

Quiero que todo se resuelva cuando se hayan resuelto todas las cadenas.

Claro, entonces simplemente pase la promesa de cada cadena en all()lugar de las promesas iniciales:

$q.all([one.promise, two.promise, three.promise]).then(function() {
    console.log("ALL INITIAL PROMISES RESOLVED");
});

var onechain   = one.promise.then(success).then(success),
    twochain   = two.promise.then(success),
    threechain = three.promise.then(success).then(success).then(success);

$q.all([onechain, twochain, threechain]).then(function() {
    console.log("ALL PROMISES RESOLVED");
});
Bergi
fuente
2
Gracias por confirmar mi peor miedo. Ahora tengo que pensar en una forma de conseguir la última promesa lol.
jensengar
¿Cuál es el problema con eso? ¿Están sus cadenas construidas dinámicamente?
Bergi
Exactamente mi problema. Estoy tratando de crear dinámicamente una cadena de promesas, luego quiero hacer algo cuando se completen las cadenas.
jensengar
¿Puede mostrarnos su código (tal vez hacer una nueva pregunta)? ¿Hay elementos agregados a la cadena después de que Q.allse ejecutó? De lo contrario, debería ser trivial?
Bergi
Me encantaría mostrarte el código ... pero aún no he terminado de escribirlo, sin embargo haré todo lo posible para explicártelo. Tengo una lista de "acciones" que deben realizarse. Estas acciones pueden tener cualquier número de niveles de subacciones asociadas a ellas. Quiero poder hacer algo cuando todas las acciones y sus subacciones estén completas. Es probable que haya varios $q.allmensajes de correo electrónico, sin embargo, una vez que comience el proceso de resolución, no se encadenarán nuevas acciones / promesas.
jensengar
16

La respuesta aceptada es correcta. Me gustaría dar un ejemplo para desarrollarlo un poco a quienes no estén familiarizados promise.

Ejemplo:

En mi ejemplo, necesito reemplazar los srcatributos de las imgetiquetas con diferentes URL espejo si están disponibles antes de renderizar el contenido.

var img_tags = content.querySelectorAll('img');

function checkMirrorAvailability(url) {

    // blah blah 

    return promise;
}

function changeSrc(success, y, response) {
    if (success === true) {
        img_tags[y].setAttribute('src', response.mirror_url);
    } 
    else {
        console.log('No mirrors for: ' + img_tags[y].getAttribute('src'));
    }
}

var promise_array = [];

for (var y = 0; y < img_tags.length; y++) {
    var img_src = img_tags[y].getAttribute('src');

    promise_array.push(
        checkMirrorAvailability(img_src)
        .then(

            // a callback function only accept ONE argument. 
            // Here, we use  `.bind` to pass additional arguments to the
            // callback function (changeSrc).

            // successCallback
            changeSrc.bind(null, true, y),
            // errorCallback
            changeSrc.bind(null, false, y)
        )
    );
}

$q.all(promise_array)
.then(
    function() {
        console.log('all promises have returned with either success or failure!');
        render(content);
    }
    // We don't need an errorCallback function here, because above we handled
    // all errors.
);

Explicación:

De los documentos de AngularJS :

El thenmétodo:

luego (successCallback, errorCallback, notifyCallback) : independientemente de cuándo se resolvió o se resolverá o rechazará la promesa, luego llama a una de las devoluciones de llamada de éxito o error de forma asincrónica tan pronto como el resultado esté disponible. Las devoluciones de llamada se llaman con un solo argumento : el resultado o el motivo del rechazo.

$ q.all (promesas)

Combina múltiples promesas en una sola promesa que se resuelve cuando se resuelven todas las promesas de entrada.

El promisesparámetro puede ser una serie de promesas.

Acerca de bind(), Más información aquí: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Hieu
fuente
El thenmétodo de $q.allproporciona una matriz de las promesas devueltas, por lo que puede repetir esa matriz y llamar thena cada elemento de la matriz, en lugar de llamar thencuando agrega la promesa a promise_array.
nick
4

Recientemente tuve este problema pero con un número desconocido de promesas. Resuelto usando jQuery.map () .

function methodThatChainsPromises(args) {

    //var args = [
    //    'myArg1',
    //    'myArg2',
    //    'myArg3',
    //];

    var deferred = $q.defer();
    var chain = args.map(methodThatTakeArgAndReturnsPromise);

    $q.all(chain)
    .then(function () {
        $log.debug('All promises have been resolved.');
        deferred.resolve();
    })
    .catch(function () {
        $log.debug('One or more promises failed.');
        deferred.reject();
    });

    return deferred.promise;
}
SoniCue
fuente
No es jQuery.map () sino Array.prototype.map () ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ) pero este enfoque funciona.
Anastasia
0

Puede utilizar "await" en una "función asíncrona" .

app.controller('MainCtrl', async function($scope, $q, $timeout) {
  ...
  var all = await $q.all([one.promise, two.promise, three.promise]); 
  ...
}

NOTA: No estoy 100% seguro de que pueda llamar a una función asíncrona desde una función no asíncrona y obtener los resultados correctos.

Dicho esto, esto nunca se usaría en un sitio web. Pero para la prueba de carga / prueba de integración ... tal vez.

Código de ejemplo:

async function waitForIt(printMe) {
  console.log(printMe);
  console.log("..."+await req());
  console.log("Legendary!")
}

function req() {
  
  var promise = new Promise(resolve => {
    setTimeout(() => {
      resolve("DARY!");
    }, 2000);
    
  });

    return promise;
}

waitForIt("Legen-Wait For It");

Flavouski
fuente