Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Synchronous loop in Promise all

I would like to do a synchronous loop in a part of my code. The function saveInDatabase checks if the item title (string) already exists in the database. That's why it can't be resolved in parallel, otherwise the condition will never apply (and would create duplicates).

Promise.all(arr.map(item => {
     saveInDatabase(item).then((myResult) => ... );
}));

I tried to encapsulate this function into separate promises, also tried with npm packages (synchronous.js, sync), but it seems that it does not fit with my code.

Maybe this solution is completely silly. Do you think it's a better idea to replace promise.all by a synchronous loop (foreach for example) ? The problem is that I need the results of each iteration...

I'm using Node 6.11.2. Could you give me some tips to handle that ? Thank you in advance.

like image 575
Yukeer Avatar asked Jan 29 '18 16:01

Yukeer


1 Answers

Without using await (which is not in node.js v6.11.2, but would make this simpler), a classic pattern for serializing a bunch of async operations that return a promise is to use a reduce() loop like this:

arr.reduce(function(p, item) {
    return p.then(function() {
        return saveInDatabase(item).then((myResult) => ... );
    });
}, Promise.resolve()).then(function() {
    // all done here
}).catch(function(err) {
    // error here
});

If you want to save all the results, you can use your .then(myResult => ...) handler to .push() the result into an array which you can access when done.

This will serialize all the calls to saveInDatabase(item) to it waits for the first one to be done before calling the second one, waits for the second one to be done before calling the third one, etc...

The default implementation here will stop if saveInDatabase(item) rejects. If you want to keep going (you don't say in your question), even when it gives an error, then you can add a .catch() to it to turn the rejected promise into a fulfilled promise.


In node.js v7+, you can use await in a regular for loop:

async function myFunc() {
    let results = [];
    for (let item of arr) {
        let r = await saveInDatabase(item).then((myResult) => ... );
        results.push(r);
    }
    return results;
}

myFunc().then(function(results) {
    // all done here
}).catch(function(err) {
    // error here
});

If you could run all the requests in parallel, then you could do that like this:

Promise.all(arr.map(item => {
     return saveInDatabase(item).then((myResult) => ... );
})).then(function(results) {
    // all done here
}).catch(function(err) {
    // error here
});

In any of these, if you don't want it to stop upon a rejection, then add a .catch() to your saveInDatabase() promise chain to turn the rejection into a resolved promise with some known value or error value you can detect.

like image 83
jfriend00 Avatar answered Sep 21 '22 11:09

jfriend00