How to use top-level await in JavaScript
By Flavio Copes
Learn how top-level await lets you use await outside an async function, dropping the IIFE boilerplate, and why it only works inside ES modules and .mjs files.
Top-level await lets you use await directly in the body of a module, outside any function. It only works in ES modules, so in Node.js you save the file as .mjs or set "type": "module" in package.json.
Let’s see why this matters.
The problem it solves
Usually can use await only inside async functions. So it’s common to declare an immediately invoked async function expression to wrap it:
(async () => {
await fetch(/* ... */)
})()
or also declare a function and then call it:
const doSomething = async () => {
await fetch(/* ... */)
}
doSomething()
Top-level await will allow us to just run
await fetch(/* ... */)
without all this boilerplate code.
With a caveat: this only works in ES modules.
How to use it in Node.js
For a single JavaScript file, without a bundler, you can save it with the .mjs extension and you can use top-level await.
Here’s a complete example. Save this as weather.mjs:
const response = await fetch('https://api.open-meteo.com/v1/forecast?latitude=45.4&longitude=11.9¤t_weather=true')
const data = await response.json()
console.log(data.current_weather.temperature)
Run it with node weather.mjs and it prints the temperature. No wrapper function needed.
If your project has "type": "module" in package.json, every .js file is already an ES module, and top-level await works there too.
In the browser, it works inside module scripts:
<script type="module">
const res = await fetch('/api/products.json')
const products = await res.json()
</script>
What happens if you use it in a regular file?
Here’s the pitfall. Try top-level await in a plain .js file loaded as CommonJS and Node throws:
SyntaxError: await is only valid in async functions and the top level bodies of modules
The error message tells you exactly what’s wrong: the file isn’t being treated as a module. The fix is one of the two options above, rename to .mjs or add "type": "module".
One more thing to know
When a module uses top-level await, any module that imports it waits for those awaited promises to settle before running its own code.
That’s convenient, because your imports always arrive fully initialized. But it also means a slow await at the top of a module delays everything that depends on it. Keep top-level awaits fast, or move slow work into a function you call when you actually need it.
Related posts about js: