Suggest an editImprove this articleRefine the answer for “Top-level await”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Top-level await is the ability to write `await` right at the top level of a module, outside any `async` function.** Before it, you had to wrap such code into a helper function like `init()` or `main()`. Now a module that contains such an `await` becomes asynchronous itself: its execution pauses until the promise settles, and every module that imports it waits along the dependency chain. It works only in ES modules (`.mjs`, `"type": "module"`, `<script type="module">`); CommonJS with `require` does not have it. ```javascript // config.mjs const env = await fetch('/env.json').then(r => r.json()); export const API_URL = env.production ? 'https://api.example.com' : 'http://localhost:3000'; ``` **Key point:** convenient asynchronous initialisation, at the cost of a long `await` delaying the start of the whole dependency tree.Shown above the full answer for quick recall.Answer (EN)Image**Top-level await is the ability to use `await` directly at the top level of an ES module, outside any `async` function.** A module with such an `await` becomes asynchronous itself: its loading pauses until the promise settles. ## Theory ### TL;DR - Previously `await` was allowed only inside an `async` function, so initialisation was wrapped into `init()` or `bootstrap()` helpers. - Now `await` can be written at the top level of a module, and the module simply waits on that line. - It works only in ES modules: `.mjs`, a `.js` file with `"type": "module"` in `package.json`, or `<script type="module">`. It is not available in CommonJS. - A module that uses top-level await effectively returns a promise when imported, so everyone importing it waits too. - Main uses: asynchronous initialisation, reading configs, dynamic imports with `await import(...)`. - Main risk: one long `await` blocks the start of the whole dependency tree. ### Quick example ```javascript // module.mjs or a file in a project with type: "module" const response = await fetch('https://api.example.com/user'); const user = await response.json(); console.log('User:', user.name); ``` The code pauses the loading of the module until the promise settles. That is exactly what makes the module asynchronous by nature. ### How it used to be Before top-level await, `await` could be used only inside an `async` function: ```javascript // This used to be an error const data = await fetch('/api/data'); // This one worked async function main() { const data = await fetch('/api/data'); console.log(await data.json()); } main(); ``` If you needed to wait for something at the beginning of a module, you had to create a wrapper function (`init()`, `bootstrap()` and the like) and remember to call it. ### Where you can use it Only in ES modules (ESM), that is in files: - with the `.mjs` extension, or - in `.js`, if `package.json` declares `"type": "module"`, or - in `<script type="module">` in the browser. In CommonJS (Node.js with `require`) it is not possible. ### How it works under the hood and how it affects other modules When a bundler or a runtime meets `await` at the top level of a module: 1. The module becomes **asynchronous**. 2. Its execution pauses until the `await` settles. 3. Other modules that **import** this module also wait until it finishes, through the dependency chain. **Example 1, an import that waits:** ```javascript // user.mjs export const user = await fetch('/api/user').then(r => r.json()); ``` ```javascript // main.mjs import { user } from './user.mjs'; console.log('User name:', user.name); ``` Here `main.mjs` will not start executing until `user.mjs` has finished its `await`. If module `A` uses a top-level `await` and module `B` imports `A`, then `B` waits for `A` as well: ```javascript // a.mjs console.log('A start'); await new Promise(r => setTimeout(r, 1000)); console.log('A done'); // b.mjs import './a.mjs'; console.log('B start'); ``` Output: ```javascript A start A done B start ``` ### Practical examples **Example 2, dynamic config initialisation:** ```javascript // config.mjs const env = await fetch('/env.json').then(r => r.json()); export const API_URL = env.production ? 'https://api.example.com' : 'https://api.staging.example.com'; ``` The `config` module can now be imported like any other, while it pulls the configuration asynchronously on its own. **Example 3, top-level await with a dynamic import:** ```javascript // main.mjs const lang = navigator.language.startsWith('fr') ? 'fr' : 'en'; const messages = await import(`./messages.${lang}.js`); console.log(messages.default.hello); ``` This lets you import a module dynamically with asynchronous syntax, without extra wrappers. **An example in Node.js:** ```json { "type": "module" } ``` ```javascript // index.js import fs from 'fs/promises'; const config = JSON.parse(await fs.readFile('./config.json', 'utf-8')); console.log('Config:', config); ``` This works because Node.js supports top-level await in ESM mode. ### Benefits, risks and a summary Benefits: - It simplifies module initialisation: no `async init()` wrapped around the code. - It removes the "import pyramid" for asynchronous dependencies. - It allows flexible asynchronous imports with `await import()`. Risks: - **Blocking the module chain:** if one module hangs on a long `await`, the whole dependency tree waits for it. - **Not available in CommonJS:** ESM only. - It can affect application startup time, especially in Node.js. | Feature | Description | | --- | --- | | What it does | Lets you use `await` outside `async` functions | | Where it works | In ES modules only | | What it returns | The module becomes asynchronous and returns a promise when imported | | Applications | Asynchronous initialisation, dynamic imports | | Risks | Blocked module loading, unavailable in CommonJS | ### Common mistakes - **Trying to use top-level await in CommonJS.** In a file without `"type": "module"` or without the `.mjs` extension it is a syntax error; the project or the file has to move to ESM first. - **Forgetting `<script type="module">`.** In a plain `<script>` top-level await does not work, because that is not a module. - **Making long network requests at the top level of a frequently imported module.** Such an `await` delays the start of everyone importing it, and the application shows nothing for a long time. Better to move it into an explicit initialisation function or to export the promise. - **Assuming top-level await makes imports parallel.** Modules in a chain wait for one another; for parallel requests inside a single module you need `Promise.all`. - **Creating circular dependencies with top-level await.** If two modules wait for each other, execution never completes. - **Not handling the error.** A rejected promise at the top level fails the loading of the entire module, so critical spots are worth wrapping in `try...catch` with a fallback value.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.