0

I have a nodejs function processReviews(workflow) that when called, is supposed to push multiple promises to an array promises[], then run them all with promises.all() after the for loop.

function examplePromiseFunc(){
    return new Promise((resolve, reject) => {
        console.log("examplePromiseFunc() INSIDE ")
        resolve('done')
    })
}

async function processReviews(workflow){
        //get objects from s3
        let allObjects = await getAllObjects(workflow);
        allObjects = allObjects.filter(obj => obj.Key.includes('output.json'))
        console.log(`found ${allObjects.length} .json files.`)

        const promises = [];
        for (let i = 0; i < allObjects.length; i++) {
            console.log('i=',i,' pushing to promises[]')
            promises.push( examplePromiseFunc() )
        }

        const result = await Promise.all(promises)
        console.log('running, result = ', result);
}

But when I run my code, the output looks like this:

found 697 .json files.
i= 0  pushing to promises[]
examplePromiseFunc() INSIDE
i= 1  pushing to promises[]
examplePromiseFunc() INSIDE
i= 2  pushing to promises[]
examplePromiseFunc() INSIDE
i= 3  pushing to promises[]
examplePromiseFunc() INSIDE
...

Which means that each time I push a promise to my promises[] array (promises.push( await examplePromiseFunc() )) the function examplePromiseFunc() is getting called instantly and is not waiting.

I want my function to only get called when I run await Promise.all(promises) at the end, is there something I'm missing? are my async functions causing a problem? I've been reading up on javascript promises.all and this seems like a fine implementation.

Martin
  • 1,336
  • 4
  • 32
  • 69
  • 3
    The fact that you are pushing `await examplePromiseFunc()` into an array and then using `Promise.all()` on that array indicates that you don't understand what either `await` or `Promise.all()` is used for. You should be using one or the other of those, not both. I'm not sure what you're trying to accomplish so don't know what to suggest. ` await examplePromiseFunc()` gets you a value, not a promise so there is no point in using `Promise.all()` on an array of values. You would use that on an array of promises (to get all the values from those promises and to know when they are all done). – jfriend00 Jan 21 '21 at 00:50
  • I am trying to push multiple function calls to promises[], so that when I run promise.all(promises) it will run multiple function calls at the same time – Martin Jan 21 '21 at 00:51
  • And, yes `await examplePromiseFunc()` calls `examplePromiseFunc()` immediately. That's how it works. – jfriend00 Jan 21 '21 at 00:51
  • 1
    Well, you're not pushing a function into the array. You're calling the function, getting a promise, awaiting that promise to get a value and pushing the value into the array. – jfriend00 Jan 21 '21 at 00:52
  • @Martin `Promise.all` does not "run" anything. There are no function calls, just promises - asynchronous result values. All `Promise.all` does is to *observe* the promises in the array. The functions are always called in the loop. And if you don't `await` between them, the asynchronous work they started will happen concurrently. – Bergi Jan 21 '21 at 01:05
  • Btw, [never pass an `async function` as the executor to `new Promise`](https://stackoverflow.com/q/43036229/1048572)! Use a proper `exampleFunction` that does something actually asynchronous, like `setTimeout`, otherwise you will never experience any concurrency. – Bergi Jan 21 '21 at 01:08
  • @Bergi updated the code in my question, i tried removing `async` from everywhere in the examplePromiseFunc() function but it didnt change anything – Martin Jan 21 '21 at 01:11
  • @Martin There's still no `setTimeout(resolve, 100)` – Bergi Jan 21 '21 at 01:13

4 Answers4

1

The issue is that you are already using await inside your loop, which means the loop will "wait" and process the items sequentially.

Instead, you should just add the promises to the array, then await all of them at the end like you had:

async function processReviews(workflow) {
  //get objects from s3
  const allObjects = await getAllObjects(workflow);

  const promises = [];
  for (let i = 0; i < allObjects.length; i++) {
    // Don't await the promise here, just start it and add it to the array.
    promises.push(examplePromiseFunc(allObjects[i]));
  }
  const result = await Promise.all(promises)
  console.log(result);
        
}
Evan Trimboli
  • 29,900
  • 6
  • 45
  • 66
  • changing `promises.push( await examplePromiseFunc() )` to `promises.push( examplePromiseFunc() )` does not make a difference, the function still runs immediately after being pushed to the array. should i keep my `examplePromiseFunc()` function be a promise? – Martin Jan 21 '21 at 00:57
  • I updated my question to include your recommended code changes, I've tried having no 'await's in my for loop but it doesnt make a difference , the function runs immediately when pushed to the array – Martin Jan 21 '21 at 01:06
  • @Martin That's what *should* happen. If your function were actually asynchronous, the multiple immediately call could start many concurrent tasks. – Bergi Jan 21 '21 at 01:09
  • thats what should happen? I thought you could use Promise.all() on an array of function calls to have them all run in parallel – Martin Jan 21 '21 at 01:12
  • @Martin - Nope. That's not what `Promise.all()` does. You pass it an array of promises and it just monitors those promises. The asycnhronous operations that produce those promises have ALREADY been called. You would benefit from reading some tutorials about using `Promise.all()`. You use it for monitoring N asynchronous operations that are already in flight. – jfriend00 Jan 21 '21 at 01:19
1

There is a fundamental misunderstanding here of of how the Promise constructor works.

The constructor takes a single function argument known as the executor:

new Promise( executor)

The executor function is called synchronously during construction with two function arguments commonly called resolve and reject:

executor( resolve, reject)

The promise executor is responsible for initiating an asynchronous operation (usually) and make the resolve and reject functions available to code that handles operation completion and error handling, often within a call back function.

Hence the code

    for (let i = 0; i < allObjects.length; i++) {
        console.log('i=',i,' pushing to promises[]')
        promises.push( examplePromiseFunc() )
    }

calls examplePromiseFunct multiple times and within that function, the executor for the promise returned is called synchronously (by Promise) during construction. Hence the log is as one would expect: a log of "examplePromiseFunc() INSIDE" each time examplePromiseFunc is called.

This misunderstanding probably leads to the second one:

Promise.all does not "run" promises - promises are passive objects that respond in deterministic ways to calling their associated resolve and reject functions, by calling either fulfilled or rejected handlers attached to them - or chaining off another promise if resolved with a promise.

Promise.all simply returns a promise that is fulfilled with an array of the fulfilled results of its argument promises, or is rejected with the rejection reason or the first promise in its argument array that becomes rejected. It has an executor in native code that effectively attaches then handlers to promises in its argument array and then passively waits (i.e. it returns to the event loop) until argument promises are settled one at a time.

traktor
  • 17,588
  • 4
  • 32
  • 53
0

that's because promises work this way only. When you are pushing a promise into an array you are waiting for it already(inside the loop) i.e if you would not wait for them to execute then also they would execute, with or without await promise.all, Also it is possible that all the promises already resolved before you pass that array in promise.all. The below function would also resolve all promises without the promise all.

async function processReviews(workflow){
        //get objects from s3
        let allObjects = await getAllObjects(workflow);
        allObjects = allObjects.filter(obj => obj.Key.includes('output.json'))
        console.log(`found ${allObjects.length} .json files.`)

        const promises = [];
        for (let i = 0; i < allObjects.length; i++) {
            console.log('i=',i,' pushing to promises[]')
            promises.push( examplePromiseFunc() )
        }
}

Also, you should not use promise.all without limit as it might reach limitations of your hardware. Using a map with a limit can reduce your problem.

async function processReviews(workflow) {
  //get objects from s3
  let allObjects = await getAllObjects(workflow);
  allObjects = allObjects.filter(obj => obj.Key.includes("output.json"));
  console.log(`found ${allObjects.length} .json files.`);

  for (let i = 0; i < allObjects.length; i = i + PROMISE_LIMIT) {
    const objects = allObjects.slice(i, i + PROMISE_LIMIT);
    const result = await Promise.all(objects.map(elem => examplePromiseFunc()));
    console.log("running, result = ", result);
  }
}
Raghu Chahar
  • 1,637
  • 2
  • 15
  • 33
0

Nothing just you have to use for in or for of loop in this type of case because forEach is some time not working in asynchronous function.

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 09 '23 at 20:58