Top-Level Await: Module Orchestration
Transform how your application initializes. Learn to use `await` outside of `async` functions to manage module-level dependencies, dynamic imports, and resource allocation with precision.
The Async Evolution
Historically, the `await` keyword was strictly bound to the scope of an `async` function. To initialize a module asynchronously, engineers were forced to use **Immediate Invoked Function Expressions (IIFEs)** or complex promise chains. **Top-Level Await** elevates the module itself to an asynchronous context, allowing for cleaner, declarative initialization logic.
// Structural Logic: Clean Initialization (ES2022)
// Before: Needed IIFE or async wrapper to use await
// After: Module evaluation itself is the "async function"
// database.js
const dbConnection = await createPool({ host: 'db.prod' });
export const query = (sql) => dbConnection.run(sql);
// app.js
import { query } from './database.js';
// Importer AUTOMATICALLY waits for 'database.js' to resolve its
// Top-Level Awaits before running its own code.
const results = await query('SELECT * FROM users');Conditional Dependency Resolution
One of the most powerful use cases for Top-Level Await is **Conditional Loading**. Instead of bloating your bundle with every possible dependency, you can use dynamic `import()` combined with `await` to load only the necessary logic based on the environment, feature flags, or browser capabilities.
// Advanced Pattern: Dynamic Dependency Loading
const isProduction = process.env.NODE_ENV === 'production';
// Dynamically import only what's needed for the environment
const logger = isProduction
? await import('./prod-logger.js')
: await import('./dev-logger.js');
export const log = logger.default;Technical Insight: The Blocking Nature
Top-Level Await is a double-edged sword. While it simplifies initialization, it **blocks the evaluation** of the current module and any other module that imports it. This ensures that when you `import` a variable, it is fully resolved and ready to use. However, a slow network request in a Top-Level Await can delay your entire app's startup. Always use high-performance CDNs or timeouts when using this pattern.
Comparison: IIFE vs. Top-Level Await
Previously, if a module needed to load data before exporting, you might have exported a `Promise`. Importers then had to `await` that promise every time they used the module. Top-Level Await shifts that burden to the **Module Loader**, making the imported value immediately available as a final result.
Top-Level Await Checklist:
- ✅ **Modularity:** Use for essential initialization like DB connections or API keys.
- ✅ **Performance:** Keep Top-Level Awaits as shallow/fast as possible to avoid boot-up lag.
- ✅ **Safety:** Always wrap Top-Level Awaits in `try/catch` to handle network or initialization failures.
- ✅ **Alternatives:** For non-critical data, consider exporting a `Promise` instead of blocking the module.
- ✅ **Capability:** Remember this feature is only available in **ES Modules**, not standard scripts.