简体   繁体   中英

How to wait for a loop to finish running before sending back a response in NodeJs?

I'm trying to send back an array of objects after a GET request to a URL but I can't figure out how to structure the logic so the return array fully populates before sending it to the client.

Below is my server side code for responding to the request. I used passport.js previously in the code to create a login portal and user object. I'm trying to take the "connections" array from the user who is making the request and sending them back an array of objects with the connection's name and picture. I know that the code below is incorrect syntax wise but it's an overview of what I'm trying to accomplish. I've tried doing it the callback way but that just kept leading me in circles because I couldn't figure out the correct logic for it.

router.get('/data', function(req, res, next) {
    var IdArr = req.user.connections;

    var retArr = [];

    function getUsers() {
        for (i = 0; i < IdArr.length; i++) {
            User.getUserById(IdArr[i], function(err, patient) {
                retArr[i] = {name:patient.name, pic:patient.profileImage};
            });
        }
    }

    function getDataAndSend() {
        function(getUsers(), function sendRes() { // I know this is incorrect syntax
            console.log(retArr);
            res.json(retArr);
        });
    }

    getDataAndSend();
});

First of all, you should declare i , and when you do it should be with block-scope ( let ), so that the nested callback function will use the same variable.

You could check how many entries in retArr have been retrieved and call res.json once you know you have them all.

router.get('/data', function(req, res, next) {
    var IdArr = req.user.connections;
    var retArr = [];
    for (let i = 0; i < IdArr.length; i++) {
        User.getUserById(IdArr[i], function(err, patient) {
            retArr[i] = {name:patient.name, pic:patient.profileImage};
            if (Object.keys(retArr).length === IdArr.length) res.json(retArr);
        });
    }
});

I think the easiest way to deal with these things is with Promises. Any async function written in the callback way (eg this User.getUserById) can be turned into a function that returns a promise. You just wrap the call around a new Promise and resolve when you're done.

Say in your case.

function promiseGetById(id) {
    return new Promise((resolve, reject) =>
        User.getUserById(id, (err, pat) => resolve(pat))
    );
}

Then something like

Promise.all(IdArr.map(id => promiseGetById(id))).then(arr =>
    res.json(
        arr.map(patient => ({ name: patient.name, pic: patient.profileImage }))
    )
);

Alternatively if you don't like promises you can do it by having a counter or something which in each callback you increment and then in your callback you res.json when the counter is the correct value.

Change all your function logic to return promises and use async/await for code clarity.

const getUserById = (id) => {
  return new Promise((resolve, reject) => {
    User.getUserById(IdArr[i], function(err, patient) {
      resolve({name:patient.name, pic:patient.profileImage});
    });
  });
}

const getAllUsers = async(idArr) => {
  const retArr = [];

  // for...of loop to await without continuing the loop
  // but this will execute only sequentially
  for (let id of idArr) {
    const ret = await getUserById(id);
    retArr.push(ret);
  }

  // for parallel execution, use Promise.all()
  await Promise.all([...idArr.map(id => getUserById(id))]);

  return retArr;
}

router.get('/data', async (req, res, next) => {
  var IdArr = req.user.connections;
  var retArr = await getAllUsers(IdArr);

  console.log(retArr);
  res.json(retArr);
});

You seem to be trying to extract way to much functions for an algorithm that isn't that complicated. The code below initialy extract the information it needs for the request. Then, it populates the array needed for the response and simply sends it.

router.get('/data', (req, res, next) => {
    const idArray = req.user.connections;

    let responseArray = [];

    // loop to populate responseArray
    for (let id of idArray) {
        User.getUserById(id, (err, patient) => {
            // add to the end of the array the patient informations
            responseArray.push({
                name: patient.name,
                pic: patient.profileImage
            });
        });
    }

    // send back responseArray
    res.json(responseArray);
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM