How to handle promises in nested loop

20
August 21, 2019, at 02:50 AM

I have an array of objects like this:

results from an aggregate give me a result with same structure as follows:

results = [
  {id: 1, test: biology, candidates:[ {cid: 11},{cid: 12},{cid: 13}]},
  {id: 2, test: chemistry, candidates:[ {cid: 15},{cid: 16},{cid: 17}]},
  {id: 3, test: physics, candidates:[ {cid: 1},{cid: 6},{cid: 7}]}
];

So i need to loop in the array, then for each candidate call a promise function getTotalMarksPerCandidate(that has a Promise.all and resolve the variable after some computations). However, since looping in array, does not wait for promise to complete, I get promise...

Question: do you know how can i work this out so that when looping in my candidates array, it waits for results and continue? or any turnaround solutions?

So I map into the results array, then did a forEach in the candidates array, call the function getTotalMarksPerCandidate with the candidate object as parameter, push the result in a promise array. Then will resolve all promises received.

var new_Result = results.map( function (subject) {
 let promises = [];
 if (subject.candidates && subject.candidates.length > 0) {
  subject.candidates.forEach( (element) => {
   promises.push(mobileUtil. getTotalMarksPerCandidate(element));
  });
 }
 return Promise.all(promises).then( result => {
  console.log('Check this out', result);
  subject.newCandidatelist = result;
  return subject;
 });
});
console.log('R: ', new_Result);
resolve(params);

So in the return Promise.all(promises) callback function, I can see at that the promises are being resolved and i see the returning value from the function getTotalMarksPerCandidate. How can i do to set the new candidates object with the totalMarks to their respective parent object like

new_results = [
{id: 1, test: biology, candidates:[ {cid: 11, score: 88},{cid: 12, score: 90},
  {cid: 13, score: 91}]},
{id: 2, test: chemistry, candidates:[ {cid: 15, score: 91},{cid: 16, score: 91}, 
  {cid: 17, score: 91}]},
{id: 3, test: physics, candidates:[ {cid: 1, score: 91},{cid: 6, score: 91},
  {cid: 7, score: 91}]}
];

Thank you

Answer 1

One approach to this that is based solely on a nested Promise chain would be as follows:

/*
Obtain array of promises via results.map() and resolve each sequentially
*/
const new_Result = Promise.all(results.map((subject) => {
    /* 
    Start a promise chain for this subject 
    */
    return Promise.resolve()
    .then(() => {
        /* 
        Get candidates from current subject, or just an empty array if no valid candidates data present 
        */
        const candidates = Array.isArray(subject.candidates) && subject.candidates.length > 0 ? subject.candidates : [];
        /*
        Map candidates (if any) to this Promise.all() 
        */
        return Promise.all(candidates.map((candidate) => {
            return mobileUtil.getTotalMarksPerCandidate(element).then(function(score) {
                /*
                Assign the score returned from getTotalMarksPerCandidate() for current candidate object
                */
                candidate.score = score;
                /*
                Return the updated candidate to mapped array of promise results
                */
                return candidate;
            })
        }));
    })
    .then((result) => {
        console.log('Check this out', result);
        subject.newCandidatelist = result;
        return subject;
    });
}));

The key addtion here are these lines, which basically "attaches" the score value returned from the resolved promise of getTotalMarksPerCandidate() to the current candidate being iterated:

return mobileUtil.getTotalMarksPerCandidate(element).then(function(score) {
    candidate.score = score;
    return candidate;
})

Hope that helps!

Answer 2

With async/await the flow can be

(async () => {
    try {
        const new_Result = results.map(({ candidates, ...rest }) => {
            const updatedCandidates = candidates.map(async candidate => {
                const score = await getTotalMarksPerCandidate(candidate)
                return { score, ...candidate }
            })
            return  { candidates: updatedCandidates, ...rest }
        })
        console.log(new_Result)
    } catch(err) {
        // handle error
    }
})()
READ ALSO
How to stub require('firebase-admin').auth().getUserByEmail() with sinon?

How to stub require('firebase-admin').auth().getUserByEmail() with sinon?

Could you please share some example of Sinon stub for firebase-admin authentificationThe challenge is to initialize firebase admin app for further stubs

52
Turning off Strict Mode in Angular?

Turning off Strict Mode in Angular?

I'm running into this issue and I'd like to turn off strict mode to get around it

33
How to run an isolated Node.js environment inside browser?

How to run an isolated Node.js environment inside browser?

I am trying to use isomorphic-git to perform git clone command on browser-sideBut due to adhering to the same-origin policy, I cannot send a cross-origin request to our company's GitLab, if no cors-proxy I can use

54
Print Object Value from Variable in NodeJS

Print Object Value from Variable in NodeJS

I want to display object value that i save in variableHow it works in NodeJS?

43