reedb89 reedb89 - 1 month ago 28
Javascript Question

async waterfall not saving correctly with mongoose

This is my first time working with async and although i have searched the web and tried both the series and waterfall methods, my results are the same. I think i am pretty close to having this work correctly but just cant get it to work 100%.

I am attempting to create a test based upon time and test question parameters sent from the front end.The main issue is that the final method at the bottom which creates the test is taking place before middle method which searches for the questions, sample answers if there are any, and then places it in an array of objects.

Once completed, test is being created with no questions in it, but it does save the time from the first function. Initially(before trying async) i wrapped the "new interviewtest" method in a 2sec setTimeout and this worked fine but i know there is a better, more efficient way to do this.

exports.create = function(req) {
console.log(req.body);
var testTime = 0;
var timer = req.body.timeValue;
var testQuestions = req.body.category;
var finalQuestions = [];

async.waterfall([
function(callback) { **add total time into one value to be saved in the test**
for (var i = 0; i < timer.length; i++) {
if (timer[i] !== '') {
testTime += parseInt(timer[i]);
}
}
callback(null,testTime);
},
function(testTime,callback) { **find question and push it into array of objects**
for (var i = 0; i < testQuestions.length; i++) {
allTestQuestions.findById(testQuestions[i], function(err, result) {
var test = {};
test.text = result.text;
test.answer = '';
test.sample = result.sample;
finalQuestions.push(test);
});
}
callback(null, testTime, finalQuestions);
},
function(testTime, finalQuestions, callback) { **create new test**
new interviewTest({
authCode: req.body.intAuthCode,
name: req.body.intName,
questions: finalQuestions, **questions**
assignedDate: new Date,
started: false,
startedTime: '1970',
completed: false,
completedTime: '1970',
time: testTime,
timePerQuestion: []
}).save(function(err, res) {
console.log(err);
if (!err) { console.log(res + " saved!"); }
});

callback(null, 'done');
}
], function (err, result) {
if (err) { callback(err); }
console.log(result);
});
}

Answer

In the middle function you have a for loop which is calling database and you are calling callback outside the loop. It should be wrapped in another async function. Try async.each or as Bergi suggests, async.map

async.waterfall([
    function(callback) {
        for (var i = 0; i < timer.length; i++) {
            if (timer[i] !== '') {
                testTime += parseInt(timer[i]);
            }
        }
        callback(null,testTime);
    },
   function(testTime,callback) {
    async.each(testQuestions, function(tQuestion, eachCallback){
        allTestQuestions.findById(tQuestion, function(err, result) {
            if(err){
                // you can either eachCallback(err) or just console.log it and keep going
            }
            var test = {};
            test.text = result.text;
            test.answer = '';
            test.sample = result.sample;
            finalQuestions.push(test);
            eachCallback()
        })
    }, function(err, result){
        callback(null, testTime, finalQuestions);
    })
},
    function(testTime, finalQuestions, callback) {
        new interviewTest({
            authCode: req.body.intAuthCode,
            name: req.body.intName,
            questions: finalQuestions,
            assignedDate: new Date,
            started: false,
            startedTime: '1970',
            completed: false,
            completedTime: '1970',
            time: testTime,
            timePerQuestion: []
        }).save(function(err, res) {
                console.log(err);
                if (!err) { console.log(res + " saved!"); }
                callback(null, 'done'); //call here
            });

      //  callback(null, 'done'); this should always be called inside the callback function
    }
], function (err, result) {
    if (err) { callback(err); }
    console.log(result);
});