Top-level await
Basic Concepts of Top-Level Await
ECMAScript 12 introduced the top-level await
feature, allowing the direct use of the await
keyword at the module level. Previously, await
could only be used inside async
functions, which required additional wrapper functions for module initialization. Top-level await
removes this restriction, making the organization of asynchronous code more intuitive.
// Previous approach
(async function() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
})();
// With top-level await
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
How It Works and Execution Order
Top-level await
pauses module execution until the awaited Promise is resolved. This affects the construction of the module dependency graph:
- The module loader first resolves all static imports.
- It then executes the module code until the first
await
is encountered. - The current module's execution is paused, and the loader proceeds to load dependent modules.
- Once the awaited Promise is resolved, the remaining code is executed.
// moduleA.mjs
console.log('Module A starts');
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Module A continues');
// moduleB.mjs
import './moduleA.mjs';
console.log('Module B runs');
The execution order will be:
- Module A starts
- Module B runs
- (After 1 second) Module A continues
Practical Use Cases
Dynamic Module Imports
Top-level await
is ideal for scenarios requiring conditional dynamic module imports:
const locale = navigator.language;
const messages = await import(`./locales/${locale}.mjs`);
console.log(messages.hello);
Configuration Initialization
You can wait for configuration loading at the module level:
const config = await (await fetch('/config.json')).json();
export function getApiUrl() {
return config.apiUrl;
}
Resource Preloading
const [userData, productList] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/products').then(r => r.json())
]);
export { userData, productList };
Error Handling Mechanisms
Error handling for top-level await
requires wrapping with try/catch
:
try {
const data = await fetchUnreliableApi();
console.log(data);
} catch (error) {
console.error('Failed to load data:', error);
// Fallback values can be set
const data = getFallbackData();
}
Impact on Module Dependencies
Top-level await
changes module resolution order:
// a.mjs
console.log('a1');
await 0;
console.log('a2');
// b.mjs
import './a.mjs';
console.log('b1');
Output order will be: a1 → b1 → a2
Browser and Node.js Support
Modern browsers and Node.js support top-level await
:
- Chrome 89+
- Firefox 89+
- Safari 15+
- Node.js 14.8+ (requires ES modules, file extension
.mjs
or"type": "module"
inpackage.json
)
Performance Considerations
When using top-level await
, note:
- It may delay module availability.
- Overuse can slow down application startup.
- Suitable for initializing critical resources but not for frequently called logic.
// Not recommended - re-waits on every import
export const data = await fetchData();
// Recommended - waits only once
const _data = await fetchData();
export function getData() { return _data; }
Interoperability with CommonJS
In Node.js, top-level await
works only in ES modules. CommonJS modules require wrapping with an async
function:
// Equivalent in CommonJS
async function main() {
const data = await fetchData();
module.exports = { data };
}
main();
Debugging Tips
When debugging top-level await
code:
- Browser developer tools now support direct debugging of module-level
await
. - Add logs before and after
await
statements. - Use Promise state inspection tools.
console.time('API load');
const data = await fetchData();
console.timeEnd('API load');
Combined Usage Example
Example combining top-level await
with other ES features:
// Configuration loading module
const configPromise = fetch('/config').then(r => r.json());
// Wait for configuration to load
export const config = await configPromise;
// Initialize services based on configuration
export const apiClient = createApiClient(config.apiUrl);
// Load other resources in parallel
export const [user, products] = await Promise.all([
fetch(`${config.apiUrl}/user`).then(r => r.json()),
fetch(`${config.apiUrl}/products`).then(r => r.json())
]);
Limitations and Considerations
- Cannot be used in non-module scripts.
- Cannot be used directly inside functions or classes.
- Affects static analysis of modules.
- Circular dependencies may cause deadlocks.
// Incorrect example - cannot use directly inside functions
function getData() {
const data = await fetchData(); // SyntaxError
return data;
}
Usage with TypeScript
TypeScript 3.8+ supports top-level await
with tsconfig.json
configuration:
{
"compilerOptions": {
"module": "esnext",
"target": "es2017"
}
}
Usage example:
interface Config {
apiUrl: string;
timeout: number;
}
const config: Config = await fetch('/config').then(r => r.json());
Integration with Build Tools
Major build tools support top-level await
:
- webpack 5+ supports it natively.
- Rollup requires the
@rollup/plugin-commonjs
plugin. - Vite supports it natively.
- Parcel 2+ supports it natively.
webpack configuration example:
// webpack.config.js
module.exports = {
experiments: {
topLevelAwait: true
}
};
Testing Strategies
When testing modules using top-level await
:
- Use a test runner that supports ES modules (Jest 27+).
- Additional configuration may be required.
- Consider using dynamic mocks.
// __mocks__/api.js
export const fetchData = jest.fn(() => Promise.resolve({ data: 'mock' }));
// test.js
import { fetchData } from '../api';
import { processedData } from '../moduleUsingTopLevelAwait';
describe('module tests', () => {
beforeAll(async () => {
await processedData; // Wait for top-level await to complete
});
test('should process data', () => {
expect(processedData).toEqual({ processed: true });
});
});
Combining with Async Iterators
Top-level await
can be used with async iterators:
async function* asyncData() {
yield await fetch('/data/1');
yield await fetch('/data/2');
}
const dataIterator = asyncData();
const firstItem = await dataIterator.next();
Dynamic Polyfill Loading
Load polyfills dynamically based on feature detection:
if (!('IntersectionObserver' in window)) {
await import('intersection-observer-polyfill');
}
// Safely use IntersectionObserver
const observer = new IntersectionObserver(callback);
Application in Server-Side Rendering (SSR)
Using top-level await
in SSR frameworks:
// Data fetching module
export const serverData = await fetchServerData();
// Page component
export default function Page({ data }) {
return <div>{data.content}</div>;
}
// During SSR rendering
export async function getServerSideProps() {
return { props: { data: serverData } };
}
Integration with Web Workers
Using top-level await
in Worker modules:
// worker.mjs
const heavyData = await processInBackground();
self.onmessage = ({ data }) => {
// Use preprocessed data
const result = heavyData[data.key];
self.postMessage(result);
};
Code Splitting Optimization
Combine with dynamic imports for smart code splitting:
const userModule = await import(
isAdmin ? './adminPanel.js' : './userPanel.js'
);
// Initialize the corresponding module
userModule.initialize();
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:静态类字段和静态私有方法