How to make a forEach loop synchronous

Viewed 440

I have an asynchronous problem here. The forEach is still running when res.json({ errors }); returns so all the errors aren't picked up. How do I deal with this?

router.post('/new/user', async function (req, res, next) {
    const validateEmail = async (email) => {
      var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
      return re.test(email);
    };
    
    const reqBody = { email, password };
    
    let errors = {};

    Object.keys(reqBody).forEach(async (field) => {
      if (field === 'email' && validateEmail(await reqBody[field])) {
        errors = { ...errors, [field]: 'Not a valid Email' };
      }
      console.log(3);
    
      if (field === 'password' && password !== '' && password < 4) {
        errors = { ...errors, [field]: 'Password too short' };
      }
    });
    
    if (Object.keys(errors).length > 0) {
      res.json({ errors });
    }
}
4 Answers

Use for...of to loop over the array elements instead and you can use await.

for(const field of Object.keys(reqBody)){
   //Code using await
}

using map instead of forEach to get promises Object.keys(reqBody).map(async (field) => {...} and then using Promise. allSettled to wait for all promise resolved or rejected and you can catch that error to log

router.post('/new/user', async function (req, res, next) {
    const validateEmail = async (email) => {
      var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
      return re.test(email);
    };
    
    const reqBody = { email, password };
    
    try{
      const promises =  Object.keys(reqBody).map(async (field) => {
      if (field === 'email' && validateEmail(await reqBody[field])) {
        Promise.reject({[field]: 'Not a valid Email'})
      }
      console.log(3);
    
      if (field === 'password' && password !== '' && password < 4) {
        Promise.reject({[field]: 'Password too short'})
      }
     });
     // List of resolved and rejected promises
     const results = await Promise. allSettled(promises)
     const errors = results.filter(result => result.status === "rejected")
     if (Object.keys(results).length > 0) {
        res.json({ errors });
     }
    }catch(error){
      res.json({ error });
    }
}

I don't like the syntax of awaiting within an async loop function. But if I were to keep the existing map structure, I would probably add an array outside it to hold blank promises. Push a promise into that array each time you loop, and then resolve it after your await and your pushing of the error. Then put await Promise.all on that array in between your object key loop and setting the response.

It's far more easy to just use a plain for loop on Object.keys(reqBody) when dealing with async stuff.

let keys =  Object.keys(reqBody)
for(let i in keys){
   ...
}
Related