web-dev-qa-db-fra.com

Assurez-angular.forChaque attente de la promesse après le prochain objet

J'ai une liste d'objets. Les objets sont passés à une fonction différée. Je souhaite appeler la fonction avec l'objet suivant uniquement après la résolution de l'appel précédent. Y a-t-il un moyen de faire ça?

angular.forEach(objects, function (object) {
    // wait for this to resolve and after that move to next object
    doSomething(object);
});
15
Razvan

Avant ES2017 et async/await (voir ci-dessous pour une option dans ES2017), vous ne pouvez pas utiliser .forEach() si vous souhaitez attendre une promesse, car les promesses ne sont pas bloquantes. Javascript et les promesses ne fonctionnent tout simplement pas de cette façon.

  1. Vous pouvez enchaîner plusieurs promesses et faire en sorte que l'infrastructure de promesse les enchaîne.

  2. Vous pouvez itérer manuellement et faire avancer l'itération uniquement lorsque la promesse précédente se termine.

  3. Vous pouvez utiliser une bibliothèque telle que async ou Bluebird qui les séquencera pour vous.

Il existe de nombreuses alternatives, mais .forEach() ne le fera pas pour vous.


Voici un exemple de séquence utilisant l'enchaînement de promesses avec des promesses angulaires (en supposant que objects est un tableau):

objects.reduce(function(p, val) {
    return p.then(function() {
        return doSomething(val);
    });
}, $q.when(true)).then(function(finalResult) {
    // done here
}, function(err) {
    // error here
});

Et, en utilisant les promesses standard ES6, ce serait:

objects.reduce(function(p, val) {
    return p.then(function() {
        return doSomething(val);
    });
}, Promise.resolve()).then(function(finalResult) {
    // done here
}, function(err) {
    // error here
});

Voici un exemple de séquencement manuel (en supposant que objects est un tableau), bien que cela ne rapporte pas l'achèvement ou des erreurs comme le fait l'option ci-dessus:

function run(objects) {
    var cntr = 0;

    function next() {
        if (cntr < objects.length) {
            doSomething(objects[cntr++]).then(next);
        }
    }
    next();
}

ES2017

Dans ES2017, la fonctionnalité async/wait vous permet d’attendre une promesse avant de poursuivre l’itération de boucle lorsque vous utilisez des boucles non basées sur des fonctions telles que for ou while:

async function someFunc() {
    for (object of objects) {
        // wait for this to resolve and after that move to next object
        let result = await doSomething(object);
    }
}

Le code doit être contenu dans une fonction async et vous pouvez ensuite utiliser await pour dire à l'interpréteur d'attendre la résolution de la promesse avant de poursuivre la boucle. Notez que bien que cela semble être un comportement de type "bloquant", cela ne bloque pas la boucle d'événement. D'autres événements dans la boucle d'événements peuvent toujours être traités pendant la await.

22
jfriend00

Oui, vous pouvez utiliser angular.forEach pour y parvenir.

Voici un exemple (en supposant que objects est un tableau):

// Define the initial promise
var sequence = $q.defer();
sequence.resolve();
sequence = sequence.promise;

angular.forEach(objects, function(val,key){
    sequence = sequence.then(function() {
        return doSomething(val);
    });
});

Voici comment cela peut être fait avec array.reduce, similaire à la réponse de @ friend00 (en supposant que objects est un tableau):

objects.reduce(function(p, val) {
    // The initial promise object
    if(p.then === undefined) {
        p.resolve(); 
        p = p.promise;
    }
    return p.then(function() {
        return doSomething(val);
    });
}, $q.defer());
8
Bjarni

vérifier $ q sur angulaire:

function outerFunction() {

  var defer = $q.defer();
  var promises = [];

  function lastTask(){
      writeSome('finish').then( function(){
          defer.resolve();
      });
  }

  angular.forEach( $scope.testArray, function(value){
      promises.Push(writeSome(value));
  });

  $q.all(promises).then(lastTask);

  return defer;
}
4
bln

Le moyen le plus simple consiste à créer une fonction et à effectuer une itération manuelle sur tous les objets du tableau après la résolution de chaque promesse.

var delayedFORLoop = function (array) {
    var defer = $q.defer();

    var loop = function (count) {
        var item = array[count];

        // Example of a promise to wait for
        myService.DoCalculation(item).then(function (response) {

        }).finally(function () {
          // Resolve or continue with loop
            if (count === array.length) {
                defer.resolve();
            } else {
                loop(++count);
            }
        });
    }

    loop(0); // Start loop
    return defer.promise;
}

// To use:
delayedFORLoop(array).then(function(response) {
    // Do something
});

Cet exemple est également disponible sur mon GitHub: https://github.com/pietervw/Deferred-Angular-FOR-Loop-Example

2
Peter-Pan

Cela a fonctionné pour moi comme ça. Je ne sais pas si c'est une bonne approche mais cela pourrait aider à réduire les lignes 

function myFun(){
     var deffer = $q.defer();
     angular.forEach(array,function(a,i) { 
          Service.method(a.id).then(function(res) { 
               console.log(res); 
               if(i == array.length-1) { 
                      deffer.resolve(res); 
               } 
          }); 
     });
     return deffer.promise;
}

myFun().then(function(res){
     //res here
});
0
ajay-annie

J'utilise une solution simple pour une connexion à une imprimante qui attend la fin de la promesse pour passer à la suivante.

angular.forEach(object, function(data){
    yourFunction(data)
    .then(function (){
        return;
    })
})
0
mat lav

Cela pourrait aider quelqu'un car j'ai essayé plusieurs des solutions ci-dessus avant de proposer le mien qui fonctionnait réellement pour moi (les autres ne l'ont pas fait)

  var sequence;
  objects.forEach(function(item) {
     if(sequence === undefined){
          sequence = doSomethingThatReturnsAPromise(item)
          }else{
          sequence = sequence.then(function(){
               return doSomethingThatReturnsAPromise(item)
                     }); 
                 }
        });
0
Lomithrani