Top-level await
Await at module top level — parent modules wait, graphs block carefully, and when to prefer async functions instead.
- javascript
- top-level
ES modules can use await at the top level — no wrapping async function main(). That makes config loading and dynamic imports cleaner, and it changes when dependent modules evaluate.
// config.js
const res = await fetch('/config.json');
export const config = await res.json();
// app.js
import { config } from './config.js';
console.log(config.apiBase);
// app.js body does not run until config.js finishes its awaits
Only in modules
// classic script → SyntaxError
// <script type="module"> or .mjs / bundler ESM output required
Node: ESM ("type": "module" or .mjs). CommonJS files cannot top-level await.
Module evaluation order
When module A imports B and B has top-level await:
- B starts loading/executing.
- Hits
await→ B’s evaluation pauses. - A waits because its import isn’t finished.
- When B’s promise settles, B finishes, then A continues.
// a.js
console.log('a start');
import './b.js';
console.log('a end');
// b.js
console.log('b start');
await new Promise((r) => setTimeout(r, 100));
console.log('b end');
// b start → (100ms) → b end → a start? actually import is hoisted:
// Execution: load graph, execute b first (dependency), pause on await,
// then complete b, then execute a.
Mental model: imports are dependencies; TLA makes a module’s “ready” promise-like.
Sibling imports
If app.js imports both slow.js (TLA) and fast.js, fast.js can still evaluate while slow.js is waiting — the graph is concurrent where possible. Don’t assume total global pause of the whole app.
Error handling
let config;
try {
config = await loadConfig();
} catch (e) {
config = defaults;
console.error(e);
}
export { config };
Uncaught rejection during module evaluation fails the module (and importers). Catch near the boundary.
When not to use TLA
| Prefer TLA | Prefer async function / lazy |
|---|---|
| Small critical config for boot | Optional features |
| Feature detection once | Per-route data |
Dynamic import() of polyfill then continue |
Large chains that block first paint |
Blocking the entire entry module on a slow network fetch delays every importer. Sometimes:
export const configPromise = fetch('/config.json').then((r) => r.json());
// importers await configPromise when needed
Bundlers and browsers
Modern bundlers support TLA with caveats (code-splitting edges, async chunks). Verify output: some older pipelines wrap modules so TLA becomes async IIFEs with different timing.
Interview answer (out loud)
“Top-level await works in ES modules so a module can pause evaluation until a promise settles; importers wait for that module to finish. It’s great for boot config and ordered setup. I avoid blocking the whole app on optional network work and I catch errors so module load doesn’t fail open.”
Dynamic import + TLA
const mod = await import(`./locales/${locale}.js`);
export const messages = mod.default;
Powerful for locale packs; ensure locale is allowlisted to avoid bundling surprises / path injection.
Circular dependencies
TLA can make circular module graphs harder to reason about because evaluation pauses mid-module. Prefer acyclic boot graphs: config → services → UI entry.
Further reading
Related
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.