Search code examples
node.jsmongodbasynchronousmongoosebluebird

async doesn't return data in callback after mongoose upgrade


In my project I use async for asynchronous queries to the database and I have this piece of code:

async.auto({
        one: function(callback){
            getFriendsIds(userId, callback)
        },
        two: ['one', function(callback, results){
            getFriendsDetails(results.one, callback);
        }],
        final: ['one', 'two', function(callback, results) {
            res.status(200).send(results.two);
            return;
        }],
    }, function(err) {
        if (err) {
            sendError(res, err);
            return;
        }
    });

The method returning friends' ids looks like this:

function getFriendsIds(userId, callback) {
    var query = User.findOne({_id: userId});

    query.exec(function(err, user) {
        if(err) {
            callback(err);
            return;
        }

        return callback(null, user.friendsIds);
    });
}

It worked perfectly. The function returned friends' ids and I used them in the "two" block of async call.

After upgrading mongoose from 4.3.7 to 4.7.8 it stopped working. I started getting Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead warning and ids were not returned in the callback anymore.

So I added bluebird package to the project and plugged it in to the mongoose. Now the warning is gone, but ids are still not returned in the callback.

I also upgraded async to the newest version, but it didn't help either.

Is there anything more I should do in order to make this work?


Solution

  • The idea of using promises is to not use callbacks, and you're still using callbacks in your code.

    Assuming you require bluebird like

    mongoose.Promise = require('bluebird');

    your function should now instead look like this:

    function getFriendsIds(userId) {
        //This will now be a bluebird promise
        //that you can return
        return User.findOne({_id: userId}).exec()
            .then(function(user){
                //return the friendIds (this is wrapped in a promise and resolved)
                return user.friendsIds;
            });
    }
    

    The returning value of the function will be an array of the user.friendsIds. Taking advantage of the chaining possibilities of promises, you can write a function that fetches the details of each friend, and return that as an array of friendDetails.

    function getFriendsDetails(friendIds) {
        //For each friendId in friendIds, get the details from another function
        //(Promise = require("bluebird") as well)
        return Promise.map(friendIds, function(friendId) {
            //You'll have to define the getDetailsOfFriend function
            return getDetailsOfFriend(friendId);
        });
    }
    

    and simply call it like

    getFriendsIds(123)
        .then(getFriendsDetails) //the resolved value from previous function will be passed as argument
        .then(function(friendDetails) {
            //friendDetails is an array of the details of each friend of the user with id 123
        })
        .catch(function(error){
           //Handle errors
        });
    

    If you want to write less code, you can let bluebird promisify the mongoose functions like this

    Promise.promisify(User.findOne)(123)
        .then(function(user){
            return user.friendsIds;
        })
        .then(getFriendsDetails)
        .then(function(friendDetails) {
            //Return or do something with the details
        })
        .catch(function(error){
            //Handle error
        });