InterviewHubPRO
Section Overview
Q487

Question 487 of 521

Part 8: Modern JavaScript APIs, WeakRef, Proxy & Performance (464 - 521)

93%
JavaScript Internals (521 Qs)•Part 8: Modern JavaScript APIs, WeakRef, Proxy & Performance (464 - 521)

What is top-level await in JavaScript modules?

Top-level await is a feature (introduced in ES2022) that allows you to use the await keyword at the top level of ES modules, outside of async functions. This enables modules to act as asynchronous functions themselves.

Key Characteristics:

  1. Module-Only: Only works in ES modules (not in scripts or CommonJS)
  2. Blocks Execution: The module graph execution pauses until the promise resolves
  3. No Async Wrapper: No need to wrap await in an async function
  4. Import Dependency: Modules that import a module using top-level await will wait for it

Before Top-Level Await:

javascript
 // ❌ Old way: Wrapper function required
 // config.js
 let config;

 async function loadConfig() {
   const response = await fetch('/api/config');
   config = await response.json();
 }

 loadConfig(); // Returns a promise, but we can't await here

 export { config }; // config might be undefined when imported!

 // ❌ Or using IIFE (Immediately Invoked Function Expression)
 (async () => {
   const response = await fetch('/api/config');
   const config = await response.json();
   // Now what? How to export?
 })();

With Top-Level Await:

javascript
 // ✅ New way: Direct top-level await
 // config.js
 const response = await fetch('/api/config');
 const config = await response.json();

 export { config }; // config is guaranteed to be loaded

Real-World Use Cases:

javascript
 // 1. Loading configuration before app starts
 // config.js
 const response = await fetch('/api/config');
 export const config = await response.json();

 // 2. Conditional module loading
 // feature.js
 const isDevelopment = process.env.NODE_ENV === 'development';
 const debugModule = isDevelopment 
   ? await import('./debug-tools.js')
   : null;

 export const debug = debugModule?.debug || (() => {});

 // 3. Database connection
 // db.js
 import { MongoClient } from 'mongodb';

 const client = new MongoClient(process.env.DB_URL);
 await client.connect();
 export const db = client.db('myapp');
 console.log('Database connected!');

 // 4. Establishing dependencies
 // auth.js
 const permissions = await fetch('/api/permissions').then(r => r.json());
 export const hasPermission = (user, action) => {
   return permissions[user]?.includes(action) || false;
 };

 // 5. Feature detection
 // capabilities.js
 let wasmSupported = false;
 try {
   await WebAssembly.instantiate(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0]));
   wasmSupported = true;
 } catch {}

 export { wasmSupported };

Module Import Blocking:

javascript
 // slow-module.js
 console.log('Starting slow module');
 await new Promise(resolve => setTimeout(resolve, 3000));
 console.log('Slow module ready');
 export const data = 'Loaded!';

 // main.js
 console.log('Before import');
 import { data } from './slow-module.js'; // Waits for top-level await
 console.log('After import:', data);

 // Console output:
 // "Before import"
 // "Starting slow module"
 // ... 3 second pause ...
 // "Slow module ready"
 // "After import: Loaded!"

Execution Order with Multiple Modules:

javascript
 // a.js
 console.log('A: start');
 await new Promise(r => setTimeout(r, 100));
 console.log('A: end');
 export const a = 'A';

 // b.js
 console.log('B: start');
 import { a } from './a.js';
 console.log('B: got', a);
 export const b = 'B';

 // main.js
 console.log('Main: start');
 import { b } from './b.js';
 import { a } from './a.js';
 console.log('Main:', a, b);

 // Output:
 // "A: start"
 // (100ms pause)
 // "A: end"
 // "B: start"
 // "B: got A"
 // "Main: start"
 // "Main: A B"

Error Handling:

javascript
 // data-loader.js
 let data;
 try {
   const response = await fetch('/api/data');
   if (!response.ok) throw new Error('Failed to fetch');
   data = await response.json();
 } catch (error) {
   console.error('Failed to load data:', error);
   data = { default: true }; // Fallback data
 }

 export { data };

Important Considerations:

  1. Performance: Top-level await blocks the entire module graph, so use sparingly
  2. Error Impact: If a top-level await rejects and isn't caught, it can prevent module loading
  3. Not for Scripts: Only works in ES modules (files with type="module" or .mjs extension)
  4. Circular Dependencies: Be careful with circular imports when using top-level await
javascript
 // ❌ Don't do this - blocks everything
 await new Promise(r => setTimeout(r, 10000)); // 10 second delay!

 // ✅ Better approach for initialization
 const dataPromise = fetch('/api/data').then(r => r.json());
 export const getData = () => dataPromise; // Let consumers decide when to await