5

I am porting a Hapi project to v17 and running into some issues with Mongoose when moving to async/await.

With any of my code that uses 'await', on a model (mongoose), object for example:

const result = await User.findOne({email: email}).exec();

I get the following exception when running 'node server.js'

            await User.findOne({}).exec();
        ^^^^^

SyntaxError: await is only valid in async function
at new Script (vm.js:74:7)
at createScript (vm.js:246:10)
at Object.runInThisContext (vm.js:298:10)
at Module._compile (internal/modules/cjs/loader.js:670:28)
at Object.Module._extensions..js 
(internal/modules/cjs/loader.js:713:10)
at Module.load (internal/modules/cjs/loader.js:612:32)
at tryModuleLoad (internal/modules/cjs/loader.js:551:12)
at Function.Module._load (internal/modules/cjs/loader.js:543:3)
at Module.require (internal/modules/cjs/loader.js:650:17)
at require (internal/modules/cjs/helpers.js:20:18)

I am running node v10.2.0 and mongoose 5.1.2 and cannot understand why I am getting the error.

The mongoosejs documentation clearly states that one should use exec() to return the promise when using async/await as stated here

Any suggestions?

1
  • 1
    well before down voting have you taken a look at the mongoosejs documentation? Since they state 'await MyModel.findOne({}).exec() if you're using async/await.' I take it that it has been tested and is an async function, otherwise why would they have it in their documentation. Plus I don't think I am the only person in the world to use hapijs and mogoose together... or am I? That is why I am asking for help... and the downvote is simply arrogant on your part! Commented May 24, 2018 at 21:02

2 Answers 2

21

await can only be used INSIDE a function that is declared with the async keyword.

async function doSomething() {
    let result = await someMongoooseFunctionThatReturnsAPromise();
    // use result here
}

await cannot be used outside an async function. That's what your error is telling you and it has nothing to do with mongoose at all. It has to do with the structure of YOUR code that is calling the mongoose function.

NOTE: Any node.js event driven code is ALREADY inside a function so to use await in that function, all you have to do is to add the async keyword to that containing function definition. If the caller of that function is not expecting any return result, then no further changes are required. If the caller of that funcition is expecting a return result, then you have to adapt the calling code to expect a promise to be returned from the async declared function.


It's also worth understanding that an async function ALWAYS returns a promise. While you may write it like regular sequential code:

async function doSomething() {
    let result = await someMongoooseFunctionThatReturnsAPromise();
    // use result here
    result++;
    return result;
}

This function is actually returning a promise and the resolved value of that promise will be the return value of the function. So, when you use an async function like this, you have to use the returned promise:

 doSomething().then(finalResult => {
     console.log(finalResult);
 });

So, in your specific code, if you're going to use await, it needs to be inside an async function:

async function someFunc() {
    const result = await User.findOne({email: email}).exec();
    // now this will work and you can use result here
}

Alternatively, you can use .then() instead:

User.findOne({email: email}).exec().then(result => {
    // process result here
    // continue with other code that uses result here
});

NOTE: To handle errors when using async/await, you have two choices:

  1. You can use traditional try/catch inside any async declared function and the try/catch will catch any rejected promises from await.

  2. If you don't use try/catch and an await inside your function rejects, then the promise that the function itself returns will become rejected and the caller of your function will get the rejected promise.

So, it depends upon the situation. If you want to handle a rejection locally, then you must use try/catch around await (much like you would with .catch(). If you want the rejecting to bubble up to the caller so they will see the rejected promise, then you don't need the try/catch as the Javascript interpreter will automatically bubble a rejected await by rejecting the promise that the async function returns.

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

9 Comments

I upvoted, your explanation is very clear. Thank you for the help.
@MarcosCasagrande - Yeah, silent downvotes are a fact of life here, but it does seem like a brief rationale would at least provide feedback to the author for what they did wrong and at best allow the author to "fix" or improve their answer - thus benefiting the whole community.
I know, it would be better to make comments mandatory when downvoting.
@MarcosCasagrande - I wouldn't go that far (sometimes the sentiment has already been expressed in a comment), but it would contribute more to the benefit of the community if some reasonable "why" was commented.
@MarcosCasagrande: this has been discussed, argued, debated, many many times on the meta site. For example: links. The communities decision has always been to not do this, as it would likely be abused, and most of us find that when commenting and down-voting, we often don't get question or answer improvements but rather we get arguments.
|
2

You need to surround your code in an async function like this

async function fetchFun() {
  const result = await User.findOne({email: email}).exec();

  console.log('Results fetched!'); //etc
}

fetchFun(); // <--

Note: You still have to call this function without await as I pointed out by arrow because there has to be some entrance to your code async/await code (like main() in C) and that entrance function cannot be called with await

4 Comments

thank you for your positive feedback, I thought of doing that but that means that I have to add an extra level of indirection for every single mongoose call. My original code which uses promises is much cleaner and elegant.
In my opinion, this would only add single level to your code and would remove somewhat messy .then() chaining
When jumping to async/await world, you would also have to use try/catch to cater for exception that you could usually do with .then(...).catch() etc. But I think async/await is worth a try!
I upvoted. thanks for the help. Not sure why someone would downvote your answer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.