DEV Community

Cover image for About "Await is only valid in Async functions" error in JavaScript
Reza Lavarian
Reza Lavarian

Posted on • Originally published at decodingweb.dev

About "Await is only valid in Async functions" error in JavaScript

Update: This post was originally published on my blog decodingweb.dev, where you can read the latest version for a 💯 user experience. ~reza

Await is only valid in Async functions; This syntax error 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).

Here’s what it looks like:

Image description

How to solve “await is only valid in async functions” error?

Await expressions are used in two ways:

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

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
function getBooks() {
    let books = await fetch('some-url/api/v1/books')
}

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

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)
Enter fullscreen mode Exit fullscreen mode

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.
})()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
} 
Enter fullscreen mode Exit fullscreen mode

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

<script type="module" src="app.js"></script>
Enter fullscreen mode Exit fullscreen mode

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 ...
}
Enter fullscreen mode Exit fullscreen mode

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
  })
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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
            })
Enter fullscreen mode Exit fullscreen mode

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 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))
Enter fullscreen mode Exit fullscreen mode

But with async/await, you won't 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()
})()
Enter fullscreen mode Exit fullscreen mode

So 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 async function. The code following await is pushed to a microtask queue to be executed once the promise being awaited 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:')
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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, prints the "Please input your email" message.

And once the promises are resolved, 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
}
Enter fullscreen mode Exit fullscreen mode

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.

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)

})()
Enter fullscreen mode Exit fullscreen mode

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 one second, fetching deals from Amazon and Etsy would take two 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)
})()
Enter fullscreen mode Exit fullscreen mode

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

I hope you found this quick guide helpful.

Thanks for reading.

Latest comments (1)

Collapse
 
Sloan, the sloth mascot
Comment deleted