Make Angular.Foreach Wait for Promise After Going to Next Object

Make angular.forEach wait for promise after going to next object

Before ES2017 and async/await (see below for an option in ES2017), you can't use .forEach() if you want to wait for a promise because promises are not blocking. Javascript and promises just don't work that way.

  1. You can chain multiple promises and make the promise infrastructure sequence them.

  2. You can iterate manually and advance the iteration only when the previous promise finishes.

  3. You can use a library like async or Bluebird that will sequence them for you.

There are lots of different alternatives, but .forEach() will not do it for you.


Here's an example of sequencing using chaining of promises with angular promises (assuming objects is an 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
});

And, using standard ES6 promises, this would be:

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

Here's an example of manually sequencing (assuming objects is an array), though this does not report back completion or errors like the above option does:

function run(objects) {
var cntr = 0;

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

ES2017

In ES2017, the async/wait feature does allow you to "wait" for a promise to fulfill before continuing the loop iteration when using non-function based loops such as for or 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);
}
}

The code has to be contained inside an async function and then you can use await to tell the interpreter to wait for the promise to resolve before continuing the loop. Note, while this appears to be "blocking" type behavior, it is not blocking the event loop. Other events in the event loop can still be processed during the await.

Wait for promises inside of a angular.forEach loop

Instead of

promises.push(deferred);

Try this:

promises.push(deferred.promise);

Angular 8 - Wait for promise to finish in loop before iterating

Seeing you are in Angular you should be working with observables. If your service returned observable instead of promises like they should be in Angular you could use an RxJs concatMap.

from(this.records).pipe(
concatMap(record => this.myService.doFirstTask(record)),
switchMap(resultOfDoFirstTask => this.myService.doSecondTask())
).subscribe(resultOfDoSecondTask => {
// Fires once for each record
});

This is the RxJs way. We tend to shy away from using promises in Angular because it is built upon RxJs.

Here is a runnable snippet for a demo.

const { of, from } = rxjs;
const { concatMap, switchMap, delay } = rxjs.operators;

const records = [1,2,3,4,5,6];

const myService = {
doFirstTask: val => of(`${val}: from first task`).pipe(delay(500)),
doSecondTask: val => of(`${val}: from second task`).pipe(delay(500)),
};

from(records).pipe(
concatMap(record => myService.doFirstTask(record).pipe(
switchMap(resultOfDoFirstTask => myService.doSecondTask(resultOfDoFirstTask))
))
).subscribe(resultOfDoSecondTask => {
console.log(resultOfDoSecondTask);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.6.0/rxjs.umd.min.js"></script>

Foreach with Promise not waiting on method results

If there is not exactly one suite in the obj, with exactly one spec, then your promise is either resolved not at all or multiple times.

Avoid wrapping too many things in the new Promise constructor - always promisify on the smallest possible level, and use promise chaining afterwards.

protractorParser.prototype.parseResults = function (fileName) {
return new Promise((resolve, reject) => {
console.log('In parseresults', fileName);
json.readFile(fileName, function (err, obj) {
if (err != null) {
console.log('error reading file', err);
reject(err);
} else {
resolve(obj);
}
});
}).then(function(obj) {
console.log('obj - ',obj);
var results = [];

for (var suite in obj) {
var specs = obj[suite].specs;
console.log('spec - ', specs);
for (let i = 0; i < specs.length; i++) {
const assert = specs[i];
const tcR = /TC[\d]+/;
const tc = assert.description.match(tcR);

let Passed = 1;
let Message = '';
let Stack = '';
if (assert.failedExpectations.length) {
const expectation = assert.failedExpectations[assert.failedExpectations.length-1];
Passed = 0;
Message = expectation.message;
Stack = expectation.stack.split('\n')[1].trim();
}
if (tc != null) {
const time = moment().utcOffset(config.get('settings.timeOffset')).format('YYYY-MM-DDTHH:mm:ss');
const promise = utility.TestDataManager.insertAutomationResults(tc[0], assert.description, Passed, process.env.testBuild, 'P', Message, Stack, 0, time, '');
results.push(promise.catch(err => {
console.log('Posting to Database failed ', err);
throw err;
}));
} else {
console.log('no test case found for test: ' + assert.description + ' -- skipping');
// I don't think you want to `throw err` here, right?
}
}
}
return Promise.all(results);
});
};

Best way to wait for .forEach() to complete

If there is no asynchronous code inside the forEach, forEach is not asynchronous, for example in this code:

array.forEach(function(item){ 
//iterate on something
});
alert("Foreach DONE !");

you will see the alert after forEach finished.

Otherwise (You have something asynchronous inside), you can wrap the forEach loop in a Promise:

var bar = new Promise((resolve, reject) => {
foo.forEach((value, index, array) => {
console.log(value);
if (index === array.length -1) resolve();
});
});

bar.then(() => {
console.log('All done!');
});

Credit: @rolando-benjamin-vaz-ferreira

Javascript/Angularjs : wait for promise to be fulfilled before going to the next iteration in foreach Loop

You could define a function to process the entries recursively like so:

function updateEntries(entries, i) {
if(i < entries.length) {
return SomeService.updateEntry(entries[i]).then(function() {
return updateEntries(entries, i+1);
});
}
}
updateEntries(entriesToUpdate, 0);

Or you could chain your promises.

var promise = SomeService.updateEntry(entriesToUpdate[0]);
for(var i = 1; i < entriesToUpdate.length; i++) {
promise = promise.then(function() {
return SomeService.updateEntry(entriesToUpdate[i]);
});
}

(These examples might not work; I'm not familiar with AngularJS promises. Only ES6 promises)

How to wait until Javascript forEach loop is finished before proceeding to next sep

Create an array of promises then use Promise.all to await their resolution/rejection using async/await.

// async/await - create an array of promises
// from function2, then await until Promise.all has
// fully resolved/rejected
async function1() {
let arr = [1, 2, 3, 4, 5];
const promises = arr.map((num) => function2(num));
await Promise.all(promises);
function3();
}

function2(number) {
return axios.post('/internal-url/action/' + number);
}

function3() {
console.log('reloading data...');
/* DB call to reload data */
console.log('data is reloaded');
}

Using async/await with a forEach loop

Sure the code does work, but I'm pretty sure it doesn't do what you expect it to do. It just fires off multiple asynchronous calls, but the printFiles function does immediately return after that.

Reading in sequence

If you want to read the files in sequence, you cannot use forEach indeed. Just use a modern for … of loop instead, in which await will work as expected:

async function printFiles () {
const files = await getFilePaths();

for (const file of files) {
const contents = await fs.readFile(file, 'utf8');
console.log(contents);
}
}

Reading in parallel

If you want to read the files in parallel, you cannot use forEach indeed. Each of the async callback function calls does return a promise, but you're throwing them away instead of awaiting them. Just use map instead, and you can await the array of promises that you'll get with Promise.all:

async function printFiles () {
const files = await getFilePaths();

await Promise.all(files.map(async (file) => {
const contents = await fs.readFile(file, 'utf8')
console.log(contents)
}));
}


Related Topics



Leave a reply



Submit