jimboweb
jimboweb

Reputation: 4542

async function in node only runs after response sent

I have the method:

const getTaskOrCategoryRecursive = async (type, id)=>{
    const rslt = await type.findOne.call(type, {_id:id},{}, standardOptions, err=>{
        if(err){
            return({err: err});
        }
    });

    const result = rslt.toObject();
    const tasks = result.tasks;
    const events = result.events;
    result.children = {};
    result.children.tasks = tasks.map(async taskId=>{
        const taskIdString = taskId.toString();
        const rtrn = await getTaskRecursive(taskIdString, err=>{
            if(err){
                return({err: err});
            }
        });
        return rtrn;
    });
    result.children.events = events.map(async eventId=>{
        const eventIdString = eventId.toString();
        await Event.findOne({_id:eventIdString}, standardOptions,err=>{
            if(err){
                return({err: err});
            }
        });
    });
    return result;
}

It's called by two methods:

const getTaskRecursive = (taskId)=>{
    return getTaskOrCategoryRecursive(Task, taskId)

}

and

const getCategoryRecursive=(categoryId)=>{
    return getTaskOrCategoryRecursive(Category,categoryId);
};

which are called by the function

router.get('/:id', verifyToken, async (req,res)=>{
    Branch.getCategoryRecursive(req.params.id)
        .then(
            (cat)=>{
                res.status(200).send(cat);
            },
            (err)=>{
                res.status(500).send(err);
            }
        )
});

When I try to run the program, first the getCategoryRecursive method runs, then res.status(200).send(cat); then the getTasksRecursive method which gets the children of the object being sent in the response. getTasksRecursive does what it is supposed to, but it's running after the response is sent so the object is empty.

How do I make my asynchronous method run before the response is sent?

UPDATE: Using Aritra Chakraborty's answer, I changed it to the following and it worked.

First I separated the .map into a new function:

const getAllTasksRecursive = async(taskIds)=>{
    const rtrn = await Promise.all(
        taskIds.map(
            async taskId=>{
                return await getTaskRecursive(taskId.toString(), err=>{if(err){return {err:err}}});
            }
        )
    )
    return rtrn;
}

Then I called it in the previous function using:

result.children.tasks = await getAllTasksRecursive(tasks);

Now I am getting the data back in the response.

Upvotes: 1

Views: 45

Answers (1)

Aritra Chakraborty
Aritra Chakraborty

Reputation: 12542

That's because internally a map or foreach can look something like this:

Array.prototype.forEach = function (callback) {
  // this represents our array
  for (let index = 0; index < this.length; index++) {
    // We call the callback for each entry
    callback(this[index], index, this)
  }
}

It will call the callback alright, but it will not wait for the callback to complete before running the next one.

So, you need one async foreach of some sort,

async function asyncForEach(array, callback) {
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array)
    }
} 

Note, how the callback is being awaited.

Your code will have to be something like this:

const start = async () => {
  await asyncForEach([1, 2, 3], async (num) => {
    await waitFor(50)
    console.log(num)
  })
  console.log('Done')
}
start()

Refer: https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404 This article helped me alot in the past.

Upvotes: 1

Related Questions