[Fixed] await is only valid in async functions and the top level bodies of modules

Wondered to how to fix 'await is only valid in async functions and the top level bodies of modules' errors? Check out options in this post!

Jan 25, 2023 | Read time 9 minutes

🔔 Table of contents

Introduction

Recently, I was converting a pretty old JavaScript library that was using a lot of callbacks - leading me down the path of “callback hell”.

So I started to convert them to a more modern pattern using async/ await to handle the asynchronous code.

I then got the dreaded error:

await is only valid in async functions and the top level bodies of modules

  

let data = await getData()
           ^^^^^

SyntaxError: await is only valid in async functions and the top level bodies of modules
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1031:15)
    at Module._compile (node:internal/modules/cjs/loader:1065:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:79:12)
    at node:internal/main/run_main_module:17:47

My code looked simple enough - its just trying to get data from a json API endpoint:

  

let data = await getData();
document.body.write(data);

async function getData() {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: 'GET',
        headers: {
          'Accept': 'application/json, text/plain, */*',
          'Content-type': 'application/json'
        }
    });
    return await res.json();
}

What does this error mean exactly?

When first reading this error, even with using await and async regularly, I had to read it a few times. The error “await is only valid in async functions and the top level bodies of modules” just means that we use the await keyword it needs to be within a async method or the await keyword is on the root of module.

We can see from my code, it is failing on the first await, because my library is NOT a module

  

let data = await getData(); /* ❌ on root, but library is not MODULE */
document.body.write(data);

async function getData() {
    ....
    ....
    return await res.json(); /* ✔️ await is inside a async function */
}

To fix the error “await is only valid in async functions and the top level bodies of modules”, we need to do the following:

  • wrap our code so that the await is inside a async function
  • Convert the code to a module - eg using the type=“module” attribute in the script tag <script type="module" src="script.js"></script>
  • Add “type”: “module” into package.json, and replace all const x = require(‘x’) with import x from ‘x’. If you don’t/can’t have package.json (e.g. a script in /usr/bin/), then change file’s extension from .js to .mjs. By the way: node script.mjs works, node script doesn’t (it says “Cannot find ‘script’"). Tested with Node.js 16.15.0 LTS and 18.2.0.
  • Use a bable plugin to transpile it: https://babeljs.io/docs/en/babel-plugin-syntax-top-level-await

An example of fixing my above code is wrapping it in a main() async function

  

async function main() {
  var data;
  await getData();
  document.body.write(data);
}

main();

What is async/await anyway?

The async and await keywords were introduced in ECMAScript 2017 (ES8), which is the 8th edition of the ECMAScript Language Specification. They were included in JavaScript as a way to handle asynchronous code in a more synchronous and readable way.

Before the introduction of async/await, JavaScript developers had to use callbacks and promises to handle asynchronous code, which could lead to callback hell, also known as the “Pyramid of Doom”, where the code becomes nested and difficult to read. With the introduction of async/await, developers can write asynchronous code in a more structured and easy-to-read way.

  

async function example() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000);
  });

  let result = await promise; // wait till the promise resolves (*)

  alert(result); // "done!"
}

example();

What is “Top-Level bodies of modules” or Top-level await?

Top-level bodies of modules or top level awaits just means that we can use the await keyword outside of a async function - given that it is used in a module.

This functionality was introduced in ECMAScript 2020 (ES11), previously this was not possible and awaits MUST be used in async or it will give the error “SyntaxError: await is only valid in async function”

As an example to this, consider that we have a helper.js module that looks like this:

  

await Promise.resolve(console.log('🎉')); /* ✔️ This is in the top-level and works now */
// Previously gives: SyntaxError: await is only valid in async function

(async function() {
  await Promise.resolve(console.log('🎉'));
  // → 🎉
}());

This just makes the code cleaner, and we chain using awaits for modules and child modules.

Lets consider the following child module of colors - it fetches the color configuration and awaits until the promise resolves:

  

// fetch request
const colors = fetch("../data/colors.json").then((response) => response.json());

export default await colors;

Now if we have a parent module that uses this child module of colors, the parent have to will wait for the child modules to execute before they themselves run, all while not blocking other child modules from loading.

Use cases for top-level awaits

This comes pretty handy and makes the code a lot cleaner - rather than callback hell.

We can use it to simply initialize configs and things like database connections:

  

const strings = await import(`/i18n/${navigator.language}`);

const connection = await dbConnector();

We can also use it to check for fallbacks:

  

let jQuery;
try {
  jQuery = await import('https://cdn-a.example.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.example.com/jQuery');
}

await is only valid in async function node js

The async and await keywords are supported in Node version 8.x.

If we want support for top-level awaits, then we need to use Node.js v14.3.0 and onwards.

An example of how to get started, lets say we want to use axios to retrieve some random data:

  1. Create Node.js project using :

npm init

  1. Now install the Axios library for fetching data from API.

npm install axios --save

  1. Now modify apackage.json file.
  

{
  "name": "async-await-top",
  "version": "1.0.0",
  "description": "",
  "main": "top-level-async-await.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.19.2"
  }
}
  1. Then in our index.js
  

import axios from 'axios';

const response = await axios('https://quote-garden.herokuapp.com/api/v2/quotes/random');
console.log(response.data);

We can then run node index.js and it will print out the random quotes!

Browser support

It’s worth noting that not all JavaScript engines support async/await. Some older browsers and JavaScript engines may not support it yet, so you should check the compatibility before using it in your project.

  • Supported in most modern browsers, just limited support in IE11 or below
  • Use await at module top level is limited in Opera - can be enabled with the #enable-javascript-harmony flag.

Summary

In this post, I went over a common error when using the async/ await syntax.

The syntax error: “await is only valid in async functions and the top level bodies of modules” just means that we are using our await keyword outside of a async function or it is not in a top-level of a module.

To fix this syntax error, we can wrap the await keyword into a async function. We then can call that async function on the top level. We can update our script tag to have the type=“module”.

When in node application, we should change our package.json to have “type”: “module” and replace all CommonJS syntax:

const x = require('x')

with ES6 module syntax

import x from 'x'

Finally, keep in mind that the await/async keyword and use in a module in the top-level has limited support in IE11 or below, but supported in most modern browsers like Chrome, Firefox, etc.

👋 About the Author

G'day! I am Huy a software engineer based in Australia. I have been creating design-centered software for the last 10 years both professionally and as a passion.

My aim to share what I have learnt with you! (and to help me remember 😅)

Follow along on Twitter , GitHub and YouTube