0

I am working on something that needs nested foreach loops in order to process some data.

Intention is there is an array of ID's I need to look up, each ID related to a user and I just need to extract their names from the response of an API call. Service A has the list of ID's and then sends a HTTP GET request to service B for each ID (can't change this), which then responds with the correct info in the format of

{
  success: true,
  user: {
    name: 'John Doe'
  }
}

Code that doesn't work but is my current code

incidents.forEach((name) => {
    foo = {
        nameID: names 
    }
    const asyncForEach = async (array, callback) => {
        for(let index = 0; index < array.length; index++) {
            await callback(array[index], index, array)
        }
    }
    const startMulti = async () => {
    await asyncForEach(foo.nameID, async (obj, index) => {
        await userController.userInfo(obj)
            .then((userInfo) => {
                foo.nameID[index] = userInfo.user.name
            })
            .then(() => {
                foobarArray.push(foo)
            })
        })
        return res.json({success: true, foobar: foobarArray})
    }
    startMulti()

})

Took the origional idea of nested foreach loops from this blog post which allowed me to do another before though this one won't work

https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404

edit show variables

let foo let foobarArray = []

Added await to usercontroller now getting proper output but error message saying Cannot set headers after they are sent to the client

Names is coming from outside the code and is just needed where it is. Not sure how to explain it without explaining the project in whole/detail.

edit show code for usercontroller.userinfo

exports.userInfo = function(id) {
    return new Promise((resolve, reject) => {
        let user = {
            _id: id
        }
        options.json = {user}
        request.get('/userInfo', options, (err, response, body) => {
            resolve(body)
        })
    })
}

This code work perfectly as expected - ie it sends request with proper payload and returns proper response.

Edit current code attempt

let foobarArray =[]
let names = []
let foo
for (const incident of incidents) {
    foo = {
        nameID: names
    }
    for (const id of incident.names) {
        const userInfo = await userController.userInfo(id)
        names.push(userInfo.user.name)

    }
}
return res.json({success: true, fooreturned: foobarArray})

Error message caused by await in front of userController

SyntaxError: await is only valid in async function

edit attempt at making async function (I normally don't use async/await instead use promises)

Even after attempt code below it still gives the error message above - I had tried same code before I edited to show error message

exports.userInfo = async function(id) {
    return new Promise((resolve, reject) => {
        let user = {
            _id: id
        }
        options.json = {user}
        request.get('/userInfo', options, (err, response, body) => {
            resolve(body)
        })
    })
}

Full code below except the userInfo function above which already is shown above.

exports.monitor = function(req, res, next) {
    const fooID = req.params.id
    let foobarArray =[]
    let names = []
    let foo
    Incident.find({fooID})
    .exec((err, incidents) => {
        if(err) {
            console.log(err)
            return res.json({success: false, foobar: []})
        }
        if(incidents != []) {           
            for (const incident of incidents) {
                foo = {
                    nameID: incident.foo[0].names
                }
                for (const id of foo.responded) {
                    const userInfo = await userController.userInfo(id)
                    names.push(userInfo.user.name)

                }
            }
            return res.json({success: true, foobar: foobarArray})
        }

    })
}

That's pretty much the whole code except some logging lines I still have to add. I pretty much need the foobar: foobarArray to be an array of objects - foo - where nameID is the array of proper names not ID's. The proper names are fetched via the userController.userInfo where the ID is passed.

edit - New code after async and promisify - not sure I did the promisify correctly

exports.monitor = async function(req, res, next) {
    const fooID = req.params.id
    const incidents = await userController.incidentFind(fooID)

}

exports.incidentFind = async function(id) {
    return new Promise((resolve, reject) => {
        const sevenAgo = moment().subtract(7, 'days').toISOString()
        let alertArray =[]
        let names = []
        let alert
        Incident.find({monitorID, createdAt: {$gte: sevenAgo}})
        .exec((err, incidents) => {
        if(err) {
            console.log(err)
            return res.json({success: false, foobar: []})
        }
        if(incidents != []) {           
            for (const incident of incidents) {
                foo = {
                    nameID: incident.foo[0].names
                }
                for (const id of foo.responded) {
                    const userInfo = await userController.userInfo(id)
                    names.push(userInfo.user.name)

                }
            }
            return res.json({success: true, foobar: foobarArray})
        }

        })
    })
}

Not sure what the actual controller monitor should contain. Bit lost

Error message

/home/me/Projects/app/incidents/controllers/users.js:100
const userInfo = await userController.userInfo(id)
                 ^^^^^

SyntaxError: await is only valid in async function

Looks like the function should be async already (put async before function name).

joshk132
  • 1,011
  • 12
  • 37
  • You never wait for the promise that chains from `userController.userInfo()`. Also there are multiple undeclared variables in your code, and you cannot send the response multiple times. – Bergi May 25 '20 at 15:37
  • @Bergi There are no undeclared variables, they are declared outside the code show. I know it sends the response multiple times, however I have no idea how to make it only send once and not skip over other code - which was an issue – joshk132 May 26 '20 at 14:03
  • Please post the entire code that is necessary to make a complete example. We can't judge what the actual problem is otherwise, and I haven't answered so far because I don't how to treat `foobarArray` in a proper solution. – Bergi May 26 '20 at 14:08
  • @Bergi There are two variables delcared outside the above code, foo. As for foobarArray it starts off as an empty array which on line 16 of the above code it show a push of `foo` into it. Then there is foo which is only referenced at foo near the top where it adds nameID. That's all the variables shown in this code. If I add the await to the userController part it gives me the correct output however it also gives me an error messaged in the node console saying `Cannot set headers after they are sent to the client` which I expected but am unsure how to resolve – joshk132 May 26 '20 at 14:17
  • You can just [edit] your question to include those :-) Also, what is `names`? – Bergi May 26 '20 at 14:32
  • "*Not sure how to explain it without explaining the project in whole/detail.*" - at least tell us what type the variable has. Is it an array? And why are you overwriting its elements in `foo.nameID[index] = …`, and why are you putting it in each of your `foo` objects? – Bergi May 26 '20 at 14:39

1 Answers1

1

I think you're looking for a simple

exports.monitor = async function(req, res, next) {
    const fooID = req.params.id
    const foobarArray = […]
    const names = […]
    try {
        const incidents = await Incident.find({fooID}).exec()
        for (const name of incidents) {
            const foo = {
                nameID: []
            }
            for (const userId of names) {
                const userInfo = await userController.userInfo(userId)
                foo.nameID.push(userInfo.user.name)
            }
            foobarArray.push(foo)
        }
        res.json({success: true, foobar: foobarArray})
    } catch(err) {
        console.log(err)
        res.json({success: false, foobar: []})
    }    
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • That looks like it might work, only issue is `foo.nameID` is an array of ID's that the userInfo replaces, how can I get the index or replace the correct one? – joshk132 May 26 '20 at 14:52
  • You probably don't want to replace them, but instead create a new array and put them in there. Updated my answer. – Bergi May 26 '20 at 14:56
  • Sure guess, just it needs to be part of fooArray because I return that back to the client so not too picky how I do it – joshk132 May 26 '20 at 14:58
  • Been playing with the code to see if I can get it to work and no luck. Put in some console.log to see the order things are getting fired off. The response is being sent back before the usercontroller is able to send and recieve the request. Also realised by console.log that it's duplicating the incidents/foo in the foobadArray – joshk132 May 26 '20 at 15:23
  • @joshk132 In the first revision of the answer, I was missing the `await` - do you have that in the code you're trying? Also, yes, it will create duplicate items, because the incident name is never used anywhere to fetch different `userInfo`. – Bergi May 26 '20 at 15:32
  • Await doesn't work with the usercontroller info due to not being async see my latest edit at the bottom of the OP to see code for usercontroller.userInfo – joshk132 May 26 '20 at 17:40
  • I have played around with console.log and your code and it seems more broken than what I had after I added that await you initally mentioned to me above. Issue is Node.js is async which means that the respone is being sent too soon. – joshk132 May 26 '20 at 18:22
  • @joshk132 In your original code, you were using `userController.userInfo(obj).then(…)`, so clearly it does return a promise and will work with `await`. You might have to mark a different function as `async`, though, there is no longer a `forEach` callback – Bergi May 26 '20 at 19:42
  • See my latest edit to the OP. It shows the error message caused by using await and also shows the current code when trying your different method. If I remove await it now just hangs and no request is ever processed, request times out – joshk132 May 27 '20 at 22:45
  • @joshk132 That's just what I'm saying! Mark the function that contains the loop as `async`. Since you still haven't shown your complete code, I can't tell you which. – Bergi May 28 '20 at 07:26
  • I got that you were saying it and I had tried it (left that out sorry) on the userInfo function provided as an edit to the OP. Specifically what other code would be helpful? I mean were in the origional or edtied code is there something that isn't clear and more code would help? – joshk132 May 28 '20 at 13:54
  • Where does `incidents` and everything come from? And *in what function* is this code located? Or is it top-level code that is not in a function? – Bergi May 28 '20 at 14:01
  • incidents comes from a DB query done on mongo via mongoose, it has a ton of info in it that is just noise far as the issue goes. it's an array of objects. What function do you want code for? Everything code that is a function is provided above albet scattered in the OP. The whole thing is a controller in an express route. – joshk132 May 28 '20 at 14:14
  • Yes, that very function. Make it `async`! – Bergi May 28 '20 at 14:26
  • I added some code and explination to my OP about making the function async - pretty sure I did it wrong – joshk132 May 28 '20 at 15:17
  • I'm not talking about `userInfo`! Relevant is the function in which the `for (const name of incidents) {` is placed. You still haven't posted that. – Bergi May 28 '20 at 15:22
  • Okay so I've added the full code that is involved in the issue. Only things left out are comments and changed some variable names to foo/foobar – joshk132 May 28 '20 at 15:48
  • Thanks. Make the `monitor` function `async` and promisify `find` so that you can use `await` for it (like `const incidents = Incident.find({fooID}).exec();`) – Bergi May 28 '20 at 15:59
  • I added another edit with the attempt at async and promisify the query – joshk132 May 28 '20 at 16:50
  • I think how to promisify that correctly should be [a new question](https://stackoverflow.com/questions/ask). – Bergi May 28 '20 at 16:58
  • I have done as requested and created a new question found at the following link. https://stackoverflow.com/questions/62072195/how-to-promisify-mongo-mongoose-find – joshk132 May 28 '20 at 18:42
  • THANK YOU! Your edit resolved the issue, I made a few changes to remove the foo/foobar and also added some stuff to `foo` variable – joshk132 May 28 '20 at 22:08