Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 751
Isaac Krementsov Avatar asked Feb 03 '18 19:02

Isaac Krementsov


1 Answers

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

like image 157
Mikhail Avatar answered Sep 23 '22 08:09

Mikhail