Aspetta che tutte le promesse si risolvano


107

Quindi ho una situazione in cui ho più catene di promesse di lunghezza sconosciuta. Voglio che alcune azioni vengano eseguite quando tutte le CATENE sono state elaborate. È anche possibile? Ecco un esempio:

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

In questo esempio, ho impostato un $q.all()per le promesse uno, due e tre che verranno risolte in un momento casuale. Poi aggiungo le promesse alle estremità di uno e tre. Voglio allche si risolva quando tutte le catene sono state risolte. Ecco l'output quando eseguo questo codice:

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

C'è un modo per aspettare che le catene si risolvano?

Risposte:


161

Voglio che tutto si risolva quando tutte le catene sono state risolte.

Certo, quindi passa la promessa di ciascuna catena in all()invece delle promesse iniziali:

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

2
Grazie per aver confermato la mia peggiore paura. Ora devo trovare un modo per ottenere l'ultima promessa lol.
jensengar

Qual è il problema con quello? Le tue catene sono costruite dinamicamente?
Bergi

Esattamente il mio problema. Sto cercando di creare dinamicamente una catena di promesse, quindi voglio fare qualcosa quando le catene sono complete.
jensengar

Puoi mostrarci il tuo codice (magari fare una nuova domanda)? Ci sono elementi aggiunti alla catena dopo che è Q.allstata eseguita, altrimenti dovrebbe essere banale?
Bergi

Mi piacerebbe mostrarvi il codice ... ma non ho ancora finito di scriverlo, comunque farò del mio meglio per spiegarlo. Ho un elenco di "azioni" che devono essere eseguite. Queste azioni possono avere un numero qualsiasi di sotto-azioni associate. Voglio essere in grado di fare qualcosa quando tutte le azioni e le loro sottoazioni sono state completate. Probabilmente ci saranno più messaggi di posta $q.allelettronica, tuttavia una volta avviato il processo di risoluzione, non verranno concatenate nuove azioni / promesse.
jensengar

16

La risposta accettata è corretta. Vorrei fornire un esempio per elaborarlo un po 'a chi non lo conosce promise.

Esempio:

Nel mio esempio, devo sostituire gli srcattributi dei imgtag con diversi URL mirror, se disponibili, prima di eseguire il rendering del contenuto.

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

Spiegazione:

Dai documenti di AngularJS :

Il thenmetodo:

then (successCallback, errorCallback, notifyCallback) - indipendentemente da quando la promessa è stata o sarà risolta o rifiutata, quindi chiama uno dei callback di successo o errore in modo asincrono non appena il risultato è disponibile. I callback vengono chiamati con un unico argomento : il risultato o il motivo del rifiuto.

$ q.all (promesse)

Combina più promesse in una singola promessa che viene risolta quando tutte le promesse di input vengono risolte.

Il promisesparametro può essere una serie di promesse.

Informazioni bind(), maggiori informazioni qui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind


Il thenmetodo di $q.allviene fornito un array delle promesse restituite, quindi puoi eseguire il ciclo di quell'array e chiamare thensu ogni elemento dell'array, invece di chiamare thenquando aggiungi la promessa a promise_array.
nick

4

Recentemente ha avuto questo problema ma con un numero sconosciuto di promesse, risolto 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;
}

Non è jQuery.map () ma Array.prototype.map () ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ) ma questo approccio funziona.
Anastasia


0

Puoi usare "await" in una "funzione asincrona" .

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

NOTA: non sono sicuro al 100% che sia possibile chiamare una funzione asincrona da una funzione non asincrona e ottenere i risultati corretti.

Detto questo, questo non sarebbe mai stato utilizzato su un sito web. Ma per test di carico / test di integrazione ... forse.

Codice di esempio:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.