Attendez que toutes les promesses se résolvent

107

J'ai donc une situation où j'ai plusieurs chaînes de promesses d'une longueur inconnue. Je veux qu'une action s'exécute lorsque toutes les CHAÎNES ont été traitées. Est-ce que c'est possible? Voici un exemple:

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

Dans cet exemple, j'ai mis en place un $q.all()pour les promesses un, deux et trois qui seront résolues à un moment aléatoire. J'ajoute ensuite des promesses aux extrémités de un et de trois. Je veux que le allse résolve lorsque toutes les chaînes ont été résolues. Voici la sortie lorsque j'exécute ce code:

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

Existe-t-il un moyen d'attendre que les chaînes se résolvent?

Jensengar
la source

Réponses:

161

Je veux que tout soit résolu lorsque toutes les chaînes ont été résolues.

Bien sûr, alors transmettez simplement la promesse de chaque chaîne au all()lieu des promesses initiales:

$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
la source
2
Merci d'avoir confirmé ma pire peur. Maintenant, je dois trouver un moyen d'obtenir la dernière promesse lol.
jensengar
Quel est le problème avec ça? Vos chaînes sont-elles construites dynamiquement?
Bergi
Exactement mon problème. J'essaie de créer dynamiquement une chaîne de promesses, puis je veux faire quelque chose lorsque la ou les chaînes sont terminées.
jensengar
Pouvez-vous nous montrer votre code (peut-être poser une nouvelle question)? Y a-t-il des éléments ajoutés à la chaîne après Q.allson exécution - sinon, cela devrait être trivial?
Bergi
J'adorerais vous montrer le code ... mais je n'ai pas encore fini de l'écrire, cependant je ferai de mon mieux pour l'expliquer. J'ai une liste des «actions» qui doivent être faites. Ces actions peuvent avoir n'importe quel nombre de niveaux de sous-actions qui leur sont associés. Je veux pouvoir faire quelque chose lorsque toutes les actions et leurs sous-actions sont terminées. Il y aura probablement plusieurs $q.alls, mais une fois que je lance le processus de résolution, aucune nouvelle action / promesse ne sera enchaînée.
jensengar
16

La réponse acceptée est correcte. Je voudrais donner un exemple pour l'élaborer un peu à ceux qui ne le connaissent pas promise.

Exemple:

Dans mon exemple, je dois remplacer les srcattributs des imgbalises par différentes URL miroir si disponibles avant de rendre le contenu.

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

Explication:

À partir de la documentation AngularJS :

La thenméthode:

then (successCallback, errorCallback, notifyCallback) - quel que soit le moment où la promesse a été ou sera résolue ou rejetée, puis appelle l'un des rappels de succès ou d'erreur de manière asynchrone dès que le résultat est disponible. Les callbacks sont appelés avec un seul argument : le résultat ou la raison du rejet.

$ q.all (promesses)

Combine plusieurs promesses en une seule promesse qui est résolue lorsque toutes les promesses d'entrée sont résolues.

Le promisesparamètre peut être un tableau de promesses.

À propos bind(), plus d'informations ici: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Hieu
la source
La thenméthode de $q.allest fournie un tableau des promesses renvoyées, vous pouvez donc boucler ce tableau et appeler thenchaque élément du tableau, par opposition à l'appel thenlorsque vous ajoutez la promesse à promise_array.
nick le
4

Récemment eu ce problème mais avec un nombre inconnu de promesses. Résolu en utilisant 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
la source
Ce n'est pas jQuery.map () mais Array.prototype.map () ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ) mais cette approche fonctionne.
Anastasia
0

Il y a un moyen. $q.all(...

Vous pouvez vérifier les éléments ci-dessous:

http://jsfiddle.net/ThomasBurleson/QqKuk/

http://denisonluz.com/blog/index.php/2013/10/06/angularjs-returning-multiple-promises-at-once-with-q-all/

Nikola Yovchev
la source
Cela m'oblige à connaître la longueur de ma chaîne, n'est-ce pas? Je veux dire que si j'avais une promesse de longueur 10, je devrais faire $q.all([p1.then(..).then(...).then(...).then(...) ...]);bien?
jensengar
0

Vous pouvez utiliser "await" dans une "fonction asynchrone" .

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

REMARQUE: je ne suis pas sûr à 100% que vous puissiez appeler une fonction asynchrone à partir d'une fonction non asynchrone et obtenir les bons résultats.

Cela dit, cela ne sera jamais utilisé sur un site Web. Mais pour le test de charge / test d'intégration ... peut-être.

Exemple de code:

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
la source