Ananbuker Ananbuker - 5 months ago 9
Javascript Question

NodeJs Initialize array inside function

I decided to use handlebars in my nodejs project so for index page, I want to collect all information related to posts,pages,categories etc.

I have a function that returns post from database like below;

exports.getPosts = function(req, res){

Posts.find({}, function(err, posts) {
var postsMap = {};

if (err){
res.status(400);
}
else{

posts.forEach(function(post) {
postsMap[post._id] = post;
});

res.jsonp(postsMap);
}
});
};


I want to change that function to the below prototype;

function getPosts(req, res){
var posts = [
{
"url": "#",
"title": "home!",
"content": "home desc"
},
{
"url":"#2",
"title": "about",
"content": "about desc)"
}
]

return posts;
}


I have tried something like below code, but posts array is not initialized and returns undefined;

function getPosts(req, res){
var posts = [];
Posts.find({}, function(err, posts) {
var postsMap = {};
if (err){
res.status(400);
}
else{
posts.forEach(function(post) {
postsMap[post._id] = post;
});
posts.push(postsMap);
}
});
return posts;
}


How can i deal with this problem?

Answer

In your final snippet of code, the function passed to Posts.find will not run until way after the function has returned

The order of execution will be (see comments):

function getPosts(req, res){
    var posts = []; //// 1
    Posts.find({}, function(err, posts) {
        var postsMap = {}; //// 3
        if (err){
            res.status(400);
        }
        else{
            posts.forEach(function(post) {
              postsMap[post._id] = post;
            });
           posts.push(postsMap);
        }
    });
    return posts; // 2
}

This is because Javascript is asynchronous and will not wait for Post.find to complete its call to the database. Instead it will keep going and will call the function(err, posts) later on.

Usually to fix this, we give callbacks to your functions. Your code can be refactored to:

function getPosts(callback){ // Note that i removed res, req from this as it is good practice to separate out request handling from data fetching. Instead I moved it to the usage function mentioned later
    Posts.find({}, function(err, posts) {
        var postsMap = {};
        if (err){
            callback(err);
        }
        else{
            posts.forEach(function(post) {
              postsMap[post._id] = post;
            });
            callback(null, postsMap);
        }
    });
}

When you use getPosts, you can do:

function otherFunction(req, res){
    getPosts(function(err, postsMap){
         // This will start running once getPosts is done

         if(err)
             res.status(400);
         else
            res.jsonp(postsMap);
    })

    // This runs almost immediately and before getPosts is done
}