2015-03-11 11 views

risposta

18

Prima ES2017 e async/await (vedi sotto per un'opzione in ES2017), non è possibile utilizzare .forEach() se si desidera attendere una promessa, perché le promesse non stanno bloccando Javascript e promesse semplicemente non funzionano in questo modo.

  1. È possibile concatenare più promesse e fare in modo che l'infrastruttura promessa le segua.

  2. È possibile scorrere manualmente e avanzare l'iterazione solo al termine della promessa precedente.

  3. È possibile utilizzare una libreria come async o Bluebird che li eseguirà in sequenza.

Ci sono molte alternative diverse, ma lo .forEach() non lo farà per voi.


Ecco un esempio di sequenziamento usando concatenazioni di promesse con promesse angolari (supponendo objects è un array):

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

E, usando promesse ES6 standard, questo sarebbe:

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

Ecco un esempio di sequenziamento manuale (supponendo che objects sia un array), t hough questo non riferire il completamento o errori come l'opzione di cui sopra fa:

function run(objects) { 
    var cntr = 0; 

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

ES2017

Nel ES2017, la funzione async/wait fa permettono di "aspettare" per una promessa di adempiere prima di continuare l'iterazione del ciclo quando si utilizza loop non basati funzione come for o 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); 
    } 
} 

Il codice deve essere contenuto all'interno di una funzione async e quindi è possibile utilizzare await per dire all'interprete di attendere la promessa da risolvere prima di continuare il ciclo. Nota, mentre questo sembra essere un comportamento di tipo "bloccante", non sta bloccando il ciclo degli eventi. Altri eventi nel ciclo degli eventi possono ancora essere elaborati durante lo await.

+1

La tua soluzione "reduce" è elegante, ma non funziona. '$ q.defer(). resolve()' risolve una promessa, ma restituisce undefined, quindi 'p' non è definito per il primo elemento. – AgDude

+0

@AgDude - Se sai cosa suggerire, ti preghiamo di proporre una modifica alla mia risposta. Non tengo il passo con tutte le diverse librerie di promesse proprietarie (quando c'è uno standard ES6 per tutto questo ora) quindi se questo non è il modo corretto di creare una promessa risolta per iniziare la catena, allora non sono – jfriend00

+0

soluzione elegante e pulita – refactor

4

assegno di $ q on angolare:

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; 
} 
+5

Questo mi sembra che esegue tutte le operazioni in parallelo e utilizza 'q.all()' per sapere quando sono tutti fatto. Ho pensato che l'OP avesse chiesto come sequenziare le operazioni una alla volta. – jfriend00

7

Sì, è possibile utilizzare angular.forEach per ottenere ciò.

Ecco un esempio (supponendo objects è un array):

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

Ecco come questo può essere fatto utilizzando array.reduce, simile a @ di friend00 risposta (supponendo objects è un array):

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()); 
+0

signore, hai ragione! questa dovrebbe essere la risposta accettata. È possibile utilizzare angular.forEach per raggiungere questo obiettivo. – bokkie

2

Il modo più semplice è creare una funzione e iterare manualmente su tutti gli oggetti nell'array dopo che ciascuna promessa è stata risolta.

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

Esempio è disponibile anche sul mio GitHub: https://github.com/pietervw/Deferred-Angular-FOR-Loop-Example

0

Potrebbe aiutare qualcuno come ho cercato molti di soluzione di cui sopra prima di venire su con il mio proprio che in realtà ha funzionato per me (gli altri non hanno)

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

Ha funzionato per me in questo modo. Non so se si tratta di un approccio giusto, ma potrebbe contribuire a ridurre le linee

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