5

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?

3 Answers 3

13

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

Sign up to request clarification or add additional context in comments.

6 Comments

This sort of works, but when I use my core module function, it returns "promise pending". How can this be fixed without a billion ".then()" callbacks?
Can you please post a sample of your code - are you using the Promise.all method or calling the await functions without it? Please show how you are calling your core module function and the exact returns/console logs
@IsaacKrementsov Sorry for the confusion, I had to test this out again. Please check out my updated code sample at the bottom of my answer. When using Promise.all, we should not have await within the functions that it acts upon - async/await is only used if you want to call the queries one after the other.
@IsaacKrementsov Also, ensure that you are resolving the promise returned by your server in a .then() clause after the fetch request on the client side. I have tested out the code at the very bottom of my post, and it works. There may have been an issue with the third example since I stitched it together from another StackOverflow answer I found
This worked for me if I did the same async functions in the core module and then made the endpoint functions asynchronous and awaited the module action (var websites = await coreModule.findUser({_id:'1234'})). Thank you so much!
|
0

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

Comments

0
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
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.