Mastertrap
Mastertrap

Reputation: 63

How to wait until async.forEachOf is complete, then send the result to client?

I have a /get route that get some data from database and send it to the user. The problem is that I use two functions with a async.forEachOf statement in them. I don't know how to wait until the second async.forEachof is complete then to send the whole result back to the user.

I think that I need to use a Promise or something like that but I can't manage how to use this in my functions. Can someone please help me this bothers me for two days already. Thank you and have a great day.

These are the functions :

function getSetsFromWorkout(workouts) {
const selectSetsQuery = "SELECT id, set_number, workoutFKEY FROM sets WHERE workoutFKEY = ?"

async.forEachOf(workouts, function (workout, key, callback) {
    const workoutId       = workout.id
    getConnection().query(selectSetsQuery, [workoutId], (err, setsRows, result) => {
        if (err) {
            console.log("Failed to query for users: "+ err)
            res.sendStatus(500)
            res.end()
        } else { 
            workout["sets"] = setsRows  
            getExercisesFromSets(workout)
        }
        callback()
    });
}, function (err) {
    if (err) console.log(err.message);
});
};

function getExercisesFromSets(sets) {
const selectExercisesQuery = "SELECT id, ex_time, type, ex_id FROM user_exercises WHERE setsFKEY = ?"

const allSets = sets["sets"]

async.forEachOf(allSets, function (set, key, callback) {
    const setId = set.id
    getConnection().query(selectExercisesQuery,[setId], (err, exercisesRows, result)=> {
        if (err) {
            console.log("Failed to query for users: "+ err)
            res.sendStatus(500)
            res.end()
        } else {
            console.log("add exercise")
            set["exercises"] = exercisesRows
            callback()
        }
    });

}, function(err) {
    if (err) console.log(err.message)
     console.log("ENDED")
     console.log(sets)
}); 
};

This is the output based on console.log() statement. The result is what i need but i don't know how to send it only after the forEachOf loop is finished.

If I put res.json(sets) here it will send back to the client only the first iteration of forEachOf then it will stop.

function(err) {
    if (err) console.log(err.message)
     console.log("ENDED")
     console.log(sets)
     **res.json(sets)**
}); 
 };

I need to wait for all the iterations to be complete, then to send the data to sclient.

add exercise
add exercise
add exercise
ENDED

//MY data from db

add exercise
ENDED
// My other set of data from db

Upvotes: 0

Views: 2360

Answers (3)

Pokeforbuff
Pokeforbuff

Reputation: 11

You can always use a promise for the inner async.forEachOf (the one in getExercisesFromSets()), by wrapping the second async code in a returned promise. And the promise will only get fulfilled when the async has finished, i.e., after the error check. Then call the first function's callback only after the second function is called and it has fulfilled the promise (i.e. in promise.then()). This will ensure a set is only counted after all the exercises in it are counted. Like this:

function getSetsFromWorkout(workouts) {
    const selectSetsQuery = "SELECT id, set_number, workoutFKEY FROM sets WHERE workoutFKEY = ?"
    async.forEachOf(workouts, function (workout, key, callback) {
        const workoutId = workout.id
        getConnection().query(selectSetsQuery, [workoutId], (err, setsRows, result) => {
            if (err) {
                console.log("Failed to query for users: "+ err)
                res.sendStatus(500)
                res.end()
            } else { 
                workout["sets"] = setsRows  
                getExercisesFromSets(workout).then(() => callback())
            }
        });
    }, function (err) {
        if (err) console.log(err.message);
        //do whatever you want with the final workouts array here
    });
};

function getExercisesFromSets(sets) {
    return new Promise((fulfill, reject) => {
        const selectExercisesQuery = "SELECT id, ex_time, type, ex_id FROM user_exercises WHERE setsFKEY = ?"
        const allSets = sets["sets"]
        async.forEachOf(allSets, function (set, key, callback) {
            const setId = set.id
            getConnection().query(selectExercisesQuery,[setId], (err, exercisesRows, result)=> {
                if (err) {
                    console.log("Failed to query for users: "+ err)
                    res.sendStatus(500)
                    res.end()
                } else {
                    console.log("add exercise")
                    set["exercises"] = exercisesRows
                    callback()
                }
            });
        }, function(err) {
            if (err) console.log(err.message)
            console.log("ENDED")
            console.log(sets)
            fulfill()
        }); 
    }); 
};

Upvotes: 1

virgiliogm
virgiliogm

Reputation: 976

I think you could create a function that will be called by the async.forEach callbacks and a variable to check if both processes have ended (for example, could be some different approaches to get the same result). Something like this:

var ended = { array1: false, array2: false };
const dataToReturn = {};

const array1 = [];
async.forEachOf(array1, function (item, key, callback) {
    // do stuff
    dataToReturn.array1 = "first data";
    callback();
}, function (err) {
    if (err) console.log(err.message);
    else {
      ended.array1 = true;
      endProcess();
    }
});

const array2 = [];
async.forEachOf(array2, function (item, key, callback) {
    // do stuff
    dataToReturn.array2 = "first data";
    callback();
}, function (err) {
    if (err) console.log(err.message);
    else {
      ended.array2 = true;
      endProcess();
    }
});

function endProcess() {
    if (!ended.array1 || !ended.array2) return;
    res.json(dataToReturn);
}

Upvotes: 1

G. Pillai
G. Pillai

Reputation: 237

If I understand correctly, you need to send the exercise sets returned from selectExercisesQuery for all workouts sets. Following changes below highlighted with comment should work:

function getSetsFromWorkout(workouts) {
const selectSetsQuery = "SELECT id, set_number, workoutFKEY FROM sets WHERE workoutFKEY = ?"

async.forEachOf(workouts, function (workout, key, callback) {
    const workoutId       = workout.id
    getConnection().query(selectSetsQuery, [workoutId], (err, setsRows, result) => {
        if (err) {
            console.log("Failed to query for users: "+ err)
            res.sendStatus(500)
            res.end()
        } else { 
            workout["sets"] = setsRows  
            getExercisesFromSets(workout)
        }
        callback()
    });
}, function (err) {
    if (err) console.log(err.message);
   res.send(workouts); // -------------------Workout object has all the info you need as it is being passed and updated by reference.
});
};

function getExercisesFromSets(sets) {
const selectExercisesQuery = "SELECT id, ex_time, type, ex_id FROM user_exercises WHERE setsFKEY = ?"

const allSets = sets["sets"]

async.forEachOf(allSets, function (set, key, callback) {
    const setId = set.id
    getConnection().query(selectExercisesQuery,[setId], (err, exercisesRows, result)=> {
        if (err) {
            console.log("Failed to query for users: "+ err)
            res.sendStatus(500)
            res.end()
        } else {
            console.log("add exercise")
            set["exercises"] = exercisesRows
            callback()
        }
    });

}, function(err) {
    if (err) console.log(err.message)
     console.log("ENDED")
     sets["sets"] = allSets //--------------assign the updated allSets array back to referenced sets.
     console.log(sets)
}); 
};

Upvotes: 0

Related Questions