12

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.

Yukeer
  • 345
  • 1
  • 2
  • 11
  • 3
    Promise.all is probably the wrong tool for the job here. You need to have one running at a time, which is better represented by a .then chain than an array passed to promise.all. `[].reduce` could handle setting up that chain for you if you have an array – Kevin B Jan 29 '18 at 16:43
  • 1
    `let looper = (current, max) => { saveInDatabase(arr[current]).then( r => { looper(current + 1, max) }) }; looper(0, arr.length);` Just to give you the idea, you know... Promise.all is conceptually wrong to me anyway (in this case) – briosheje Jan 29 '18 at 16:43
  • 1
    See [JavaScript ES6 promise for loop](https://stackoverflow.com/q/40328932/5459839). – trincot Jan 29 '18 at 16:46
  • `synchronous loop` for asynchronous code is never a solution. Promise.all isn't necessarily wrong to use, if you want to access all the results once the last iteration completes. The fact that you're using `.map` without returning anything from the `.map` callback means you're already using `.map` as if it were `.forEach` anyway! – Jaromanda X Jan 29 '18 at 22:36

1 Answers1

28

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.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thank you for your great explanation. There is something that is bothering me though. If I put all my results into an array, this won't resolve the problem of checking in the database if there is any duplicate (according to its title). I think I will modify the purpose of my function saveInDatabase : it will just return a list of all items instead of "save" litteraly in database. Then I can check the duplicate condition in the final array. Yeah I forgot to tell that an item can returns sub-items, etc... – Yukeer Jan 30 '18 at 08:35
  • It helped me understand it better by using reduce method like: `arr.reduce(function(promise, item) { await promise; ... }, Promise.resolve())` – Hossein Margani Apr 19 '22 at 06:14