How to fix the “Await is only valid in Async functions” in Node.js

Updated Mar 17, 2023 ⤳ 6 min read

The JavaScript error “Await is only valid in Async functions” occurs when you use an await expression outside an async execution context, like an async function or top-level body of an ES module (top-level await).


// ⛔ Wrong
function getBooks() {
    let books = await fetch('some-url/api/v1/books')
}

How to fix it?

First of all, we need to determine where is the await expressions. Await expressions are used in two ways:

  1. Using await expressions in async functions
  2. top-level await

1. Using await expressions in async functions: If you're using an await expression in a function, adding the async keyword to the function declaration resolves the issue.


// ⛔ Wrong
// It'll raise ↬ Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
function getBooks() {
    let books = await fetch('some-url/api/v1/books')
}

// ✅ Correct
async function getBooks() {
    let books = await fetch('some-url/api/v1/books')
}

Just beware that once you make a function async, it'll always return a promise. So if you're using the respective function in other places, remember to update your code accordingly.

You can also make callback functions async:


setTimeout(async () => {
    const items = await getItems()
}, 2000)

2. Top-level await: If your await statement isn't in a function, you can wrap your code in an async IIFE (Immediately Invoked Function Expression):


(async () => {
   let books = await fetch('some-url/api/v1/books')
   // Any code here will be executed after the books variable has the value.
})()

How about ES modules? Await can be used on its own in ES modules too.

If you're using Node js, you must set Node's module system to ES module system first; Top-level await isn't supported by the Node.js default module system (CommonJS).

To do that, add "type": "module" to your package.json file.

If you don't have a package.json file yet, run the following terminal command from your project directory:


npm init

Then, add "type": "module" to your module's configuration:


{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "type": "module",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "",
  "license": "ISC"
} 

If your module is supposed to be loaded in the browser, add "type=module" to the <script> tag, and you're good to go:


<script type="module" src="app.js"></script>

If you're curious how async/await works, please read on.

Understanding async functions in JavaScript

Async functions are easy to spot as they have the async keyword in their declaration. 


async function myFunction () {
  // await can be used here ...
}

An async function always returns its value wrapped in a Promise (if the returned value isn't already a promise). You can access the returned value once it's resolved (if not rejected). 

Let's see an example:


async function asyncTest() {
    return 1
}

let asyncFunctionValue = asyncTest()
console.log(asyncFunctionValue)
// output: Promise { 1 }

// Get the value when it's resolved
asyncFunctionValue
  .then(value => {
    console.log(value)
   // output: 1
  })

So basically, the async keyword implicitly wraps the returned value in a promise (if it's not already a promise).

The above code is equivalent to the following:


function asyncTest() {
  let returnValue = 'someValue' 
  return new Promise.resolve(returnValue)
}

Now, what's the await keyword?

The async/await duo enable you to write asynchronous code more cleanly by avoiding promise chains (a cascade of then() methods).


promiseObj
.then(value => {
// some code here
})
    .then(value => {
    // some code here
    })
        .then (value => {
        // some code here
        })
            .then(value => {
            // some code here
            })

The await keyword makes JavaScript look synchronous, even though it never blocks the main thread. The purpose of using await inside an async function is to write cleaner asynchronous code in promise-based APIs, like the Fetch API.

The only rule is, await expressions must be used inside async functions. Otherwise, you'll get the syntax error "await is only valid in async functions and the top level bodies of modules".

Let's make it clear with an example. 

Using the fetch API in the old-fashioned way is like this:


fetch('some-url/api/v1/movies')
  .then(response => response.json())
  .then(data => console.log(data))

But with async/await, you'll no longer need then() callbacks:


let response
(async () =>  {
     let movies = await fetch('some-url/api/v1/movies')
     // The code following await is treated as if they are in a then() callback
     response = await movies.json()
})()

When JavaScript encounters an await expression in your async function, it pauses the execution of the code following await and gets back to the caller that invoked the function.

The code following await is pushed to a microtask queue to be executed once the promise is resolved.

The following code is a simplified (and imaginary) chatbot that starts a chat session with a user. We have a function named say(), which returns messages after a delay (to mimic human typing).



function say(text, delay = 500) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(text)
        }, delay)
    })
}

async function startConversation() {
    console.log('Hi!')   
    console.log(await say('my name is R2B2,'))
    console.log(await say('How can I help you?'))
}

startConversation()
console.log('Please input your email:')

However, the function doesn't return the messages in the order we expect:


Hi!
Please input your email:
my name is R2B2,
How can I help you?

The reason is once JavaScript gets to the first await, it pauses the execution of what's left in the function and returns to the caller (startConveration()). The main thread that is now freed, proceeds to printing the "Please input your email" message.

And once the promises are resolved (adter the delay), the function's remaining lines are executed - as if they were inside a callback function.

It's just the good old then() callback functionality but more cleanly!

Additionally, the async/await duo lets us use try/catch with promised-based APIs. Something you couldn't simply do with then() callbacks.


let items = []
try {
    items = await getItemsFromApi()
} catch (error) {
   // Handle the error here
}

A quick note on performance ...

Since the code after the await is to be paused execution, you gotta make sure it's only followed by the code that depends on it. Otherwise, some operations will have to wait for no reason.

Let's make it clear with an example.

Imagine we need to get the best deals from Amazon and Etsy and merge the results into an array (to be listed on a web page).

The following approach isn't optimized:


function getAmazonDeals() {
    // get Amazon deals ...
}

function getEtsyDeals() {
    // get Etsy deals ...
}


// Using an IEEF function here ...
(async () => {
    const amazonDeals = await getAmazonDeals(1000)
    const etsyDeals = await  getEtsyDeals(1000)

    const allDeals = [...amazonDeals, ...etsyDeals]
    populateDealsList(allDeals)

})()

In the above example, the lines following the first await are paused until the data is fetched from Amazon. This means the second request (getEtsyDeals()) has to wait without being dependent on the return value of getAmazonDeals().

So if each request takes 1 second, fetching deals from Amazon and Etsy would take 2 seconds in total.

But what if we initiated both requests concurrently and use await afterward?

Let's see how:


function getAmazonDeals() {
    // get Amazon deals ...
}

function getEtsyDeals() {
    // get Etsy deals ...
}


// Using an IEEF function here ...
(async () => {
    // We're not using await here to initiate the requests immediately
    const amazonDeals = getAmazonDeals(1000)
    const etsyDeals = getEtsyDeals(1000)

     // Since the result of both requests are still promises, we use await when we want to combine them into one array
    // The leads aren't populated until we have deals from both sources
    const allDeals = [...await amazonDeals, ...await etsyDeals]
    populateDealsList(allDeals)
})()

Since both requests start immediately, we have both responses in 1 second.

 Alright, I think it does it! I hope you found this quick guide helpful :-).

Thanks for reading.

Author photo

Hey 👋 I'm a software engineer, an author, and an open-source contributor. I enjoy helping people (including myself) decode the complex side of technology. I share my findings on Twitter: @rlavarian

If you read this far, you can tweet to the author to show them you care. Tweet a Thanks

Disclaimer: This post may contain affiliate links. I might receive a commission if a purchase is made. However, it doesn’t change the cost you’ll pay.