Isaac Krementsov
Isaac Krementsov

Reputation: 706

How to return Mongoose query results without callback

I have an app which is structured: index > routes > endpoints > core, where endpoints process requests to send a response and core accesses the database. The point of dividing controllers in to these two components was partially to make things work after a DB change and partially to simplify my code. The issue is that I can only get the results of a mongoose query function in my module via callback, which does not simplify my code at all.
What I want:

var user = coreModule.findUser({id:123}) //return user
var posts = coreModule.findPosts({author:123}) //return posts array
res.render("home", {user:user, posts:posts})

By doing:

//core module example
findUser: function(params){
   User.findOne(params, function(err,docs){
      if(err){
         return err
      }else{
         return docs
      }
   }
}

But instead I have to use a mess of callback functions which defeat half of the purpose of core modules in the first place. I have seen that you can do this with SQL and Knex, but it will not work with mongoose. If what I am trying to do is impossible, is there a mongoose substitute that would be recommended in this case?

Upvotes: 5

Views: 15179

Answers (3)

Mikhail
Mikhail

Reputation: 900

Use the Async/Await Syntax:

const findUser = async function (params) { 
    try {  return await User.findOne(params)
    } catch(err) { console.log(err) }
}

const userSteve = findUser({firstName: Steve})

Each time you need to use the information from a Query, use the await prefix within an async function. This will allow you to use asynchronous nature of a Mongoose query within synchronous code that needs that query result to continue.

For your code:

coreModule.findUser  = async function (userId) { 
    return await User.findOne({id:_userId})
}

coreModule.findPosts = async function(authorId) {
    return await Posts.find({postedBy: authorId})
}

const foundUser = coreModule.findUser(123);
const foundPosts = coreModule.findPosts(123);
res.send({user: foundUser, posts: foundPosts}) 

If you would like both queries to fire off simultaneously, you can use Promise.all()

coreModule.findUser  =  function (userId) { 
    return User.findOne({id:_userId})
}

coreModule.findPosts = function(authorId) {
    return Posts.find({postedBy: authorId})
}

const [foundUser, foundPosts] = 
   await Promise.all(
         [coreModule.findUser(123), coreModule.findPosts(123)]
   ); 

res.send({user: foundUser, posts: foundPosts})

If you have both Queries located at separate EndPoints in your API, you could also construct two fetch requests aimed at both endpoints and fire those off simultaneously on the Client-Side with Promise.all()

I hope this helped!

Edit: I have edited my post with this working example which I have tested in my API:

module.exports.test = async function(req, res, next) {
    const module1 = function(params) {
        return ShoppingCartModel.find({_id: params})
    }
    const module2 = function(params) {
        return User.find({_id: params})
    }
    const array = [module1('5a739dbc45424d2904faca5b'), module2('5a739dbc45524d2904faca5b')]
    const promise = await Promise.all(array)
    res.json(promise)
}

Some examples of incorrect returns:

Incorrect:

const array = [ShoppingCartModel.find({}), ShoppingCartModel.find({})]
// console.log(array) = Mongoose Query Constructors
const promise = Promise.all(array)
// console.log(promise) = Promise { <pending> }

Correct:

const array = [ShoppingCartModel.find({}), ShoppingCartModel.find({})]
const promise = await Promise.all(array)

Incorrect:

// Return full fledged promise from Mongoose query using .exec()
const array = [ShoppingCartModel.find({}).exec(), ShoppingCartModel.find({}).exec()]
// console.log(array) = [ Promise { <pending> }, Promise { <pending> } ]
const promise = Promise.all(array)
// console.log(promise) = Promise { <pending> }

Correct:

const array = [ShoppingCartModel.find({}).exec(), ShoppingCartModel.find({}).exec()]    
const promise = await Promise.all(array)

You must await the result of Promise.all, or else your function will blow through the function calls, send an empty JSON object back to the front-end, and console.log pending promises which are not given time to resolve

Upvotes: 13

Kishor Patil
Kishor Patil

Reputation: 950

const findBook = async (queryParams) => { 
    try {  
         return await Book.findOne(params)
    } catch(error) { 
          // handle the errors
  }
}

const getBook = async (req, res) =>{
    try{
     const book = findBook(queryParams);
     // next code goes here
    }catch(error){
          // handle the errors
    }
}

Upvotes: 0

David Vicente
David Vicente

Reputation: 3111

As far as calling db is an asynchronous thing, you have to wait for the response in an asynchronous way. But, callbacks aren't the only way. You can use promises, with came to have cleaner code. It looked like this:

coreModule.findUser({id:123})
.then(user => {
    return coreModule.findPosts({author:123});
})
.then(posts => {
    res.render("home", {user:user, posts:posts});
});

And the mongoose part should responde a promise instead using callbacks, like this:

//core module example
findUser: function(params){
   return User.findOne(params).lean(true).exec();
}

Hope it helps

Upvotes: 0

Related Questions