Top-Level await in JavaScript Modules

Top-level `await` lets an ECMAScript module pause its evaluation until an awaited promise settles, without wrapping the code in an `async` function. This can also delay evaluation of modules that depend on it.

Top-level `await` is available in ECMAScript modules, where `await` may appear directly in the module body. The module's evaluation becomes asynchronous, allowing initialization work such as loading configuration or importing data before the module continues.

When execution reaches a top-level `await`, the module does not finish evaluating immediately. Its dependents may have to wait for that module's evaluation to complete, while unrelated modules can continue when the module graph permits it.

Top-level `await` is therefore different from simply starting an asynchronous operation and ignoring its promise. Code after the `await` observes the fulfilled result, and dependency evaluation respects the module's asynchronous completion.

In browsers, top-level `await` works in JavaScript modules, such as scripts loaded with `type="module"`. In Node.js, it applies to ECMAScript modules under the module-system rules of the relevant Node.js version.

Because a module can become an asynchronous dependency, top-level `await` should be used deliberately. A promise that never settles can prevent the module from completing evaluation and consequently block modules that depend on it.

Key points

Interview questions

What is top-level `await`, and where can you use it?

Top-level `await` allows an `await` expression directly in an ECMAScript module body, rather than only inside an `async` function. It is useful when module initialization itself depends on asynchronous work. The module's evaluation remains incomplete until the awaited operation settles. It is not generally valid in a classic script.

What happens to a module's dependents when it reaches top-level `await`?

The module becomes asynchronous during evaluation. A dependent module cannot complete its own evaluation as though the awaited module had already finished; the dependency's evaluation must reach completion first. This creates an asynchronous dependency edge in the module graph. Unrelated portions of the graph may still proceed when their dependencies allow it.

Why might a frontend application use top-level `await`?

A module may need asynchronous initialization before exposing code that relies on the result. For example, it could load configuration or obtain initialization data before continuing module setup. Top-level `await` makes that ordering explicit without creating an extra wrapper function. The tradeoff is that modules depending on this module may also have to wait.

What happens if a top-level awaited promise rejects?

The module cannot successfully complete evaluation because its awaited operation failed. The rejection propagates through module evaluation to modules whose evaluation depends on that module. Application behavior then depends on how the surrounding environment handles the module-loading or evaluation failure. Therefore, top-level asynchronous initialization should have deliberate failure semantics.

What architectural risks should you consider when using top-level `await`?

Top-level `await` makes module evaluation asynchronous, so it changes the timing characteristics of the module graph. A slow or indefinitely pending operation can delay every dependent module that requires the affected evaluation to complete. It can also make startup failures occur during module loading rather than later in an application function. A good design therefore limits top-level asynchronous work to initialization that genuinely must complete before dependent code runs.