Home > database >  NodeJS: async/await doesn't return data correctly with loops
NodeJS: async/await doesn't return data correctly with loops

Time:05-09

I'm having these calls to list users with groups from google active directory

let globalGroups = null;
let groupMembers = null; 

await GetCustomerId(req, res).then( async () => {
      // GetGroups is async function saves groups in `globalGroups` variable
      await GetGroups(token).then( () => {
            globalGroups.forEach( async (group) => {
                  // GetGroupMembers is async function saves users in `groupMembers` variable
                  await GetGroupMembers(group.id, token).then( () => {
                        groupMembers.forEach( (member) => {
                             // here I log the `member` and have no issues here
                             if (usersIds.includes(member.id)) {
                                 let user = users.find( ({ id }) => id === member.id );
                                 user.group_ids.push(group.id);
                             }
                             else {
                                  member.group_ids = [];
                                  member.group_ids.push(group.id);
                                  users.push(member);
                                  usersIds.push(member.id);
                             }
                         })
                   })
            });
            // the issue is here without timeout it returns an empty array because it doesn't wait for the loop to finish
            console.log(users);
            res.status(200).json({"users": users}).send();
           }).catch(function(err) {
               console.log(err)
               res.status(500).json({"error": err}).send();
           });
});

This returns an empty array unless I use timeout to return the response like this

setTimeout( () => {
     console.log(users);
     res.status(200).json({"users": users, "next_page_link": "notFound"}).send();
}, 1000);

How to make it wait till the whole loop ends to return the response without using timeout?

const GetCustomerId = async (req, res, next) => {
    try {
        let authorization = req.headers['authorization'].split(' ');
        if (authorization[0] !== 'Bearer') {
            return res.status(401).send();
        } else {
            await axios({
                url: 'https://admin.googleapis.com/admin/directory/v1/users?domain=&maxResults=1',
                method: 'get',
                headers: {
                    'Content-Type': "application/json",
                    'Authorization': ' Bearer '   authorization[1]
                },
            })
                .then((response) => {
                    globalCustomerId = response.data.users[0].customerId
                })
                .catch(function(err) {
                    console.log(err);
                });
        }
    } catch (err) {
        console.log(err);
    }
} 

const GetGroups = async (token) => {
  try {
    await axios({
        url: 'https://admin.googleapis.com/admin/directory/v1/groups?customer='   globalCustomerId,
        method: 'get',
        headers: {
            'Content-Type': "application/json",
            'Authorization': ' Bearer '   token
        },
    })
        .then((response) => {
            globalGroups = response.data.groups;
        })
        .catch(function (err) {
            return res.status(500).json({"error": err}).send();
        });

} catch (err) {
    return res.status(403).json(err).send();
}

}

const GetGroupMembers = async (groupId, token) => {
  await axios({
    url: "https://admin.googleapis.com/admin/directory/v1/groups/"   groupId   "/members",
    method: 'get',
    headers: {
        'Content-Type': "application/json",
        'Authorization': ' Bearer '   token
    },
})
    .then((response) => {
        groupMembers = null;
        groupMembers = response.data.members;
    })
    .catch(function (err) {
        return res.status(500).json({"error": err}).send();
    });
}

CodePudding user response:

globalGroups.forEach( async (group) => {

An async method inside .forEach doesn't actually do what you might want it to do.

By essentially doing array.forEach(async method) you're invoking a bunch of async calls, 1 per element in the array. It's not actually processing each call one by one and then finally resolving.

Switch to using a regular for loop with await inside it and it will do what you want.

eg.

for (const group of globalGroups) {
  await GetGroupMembers(group.id, token)
  groupMembers.forEach.....
}

You could do that to force your code to be more synchronous (or use something like Promise.all to be more efficient while still being synchronous) but another issue with the code is you're stuck in callback hell, which leads to less-readable code.

I'd highly recommend refactoring your Get* methods such that they return the values you need. Then you can do something cleaner and predictable/deterministic like:

const globalCustomerId = await GetCustomerId(req, res);
const globalGroups = await GetGroups(token); //note: Promise.all could help here

for (const group of globalGroups) {
  const groupMembers = await GetGroupMembers(group.id, token)
  groupMembers.forEach.....


}
console.log(users);
res.status(200).json({"users": users}).send();

You can wrap it in a try/catch to take care of error handling. This leads to much cleaner, more concise, and more predictable order of executions.

  • Related