JavaScript 顶层 Await
·
顶层 await 允许在模块的顶层作用域中直接使用 await 关键字,而不需要包装在 async 函数中。
基本概念
传统方式 vs 顶层 Await
传统方式(需要 async 函数包装):
// 传统方式
async function main() {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
}
main();
顶层 Await:
// 使用顶层 await
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
使用场景
- 模块初始化
// config.js
const config = await fetch('/api/config').then(r => r.json());
export { config };
// 在其他模块中使用
import { config } from './config.js';
console.log(config.apiUrl);
- 动态导入
// 根据条件动态导入模块
const userLocale = navigator.language;
const messages = await import(`./locales/${userLocale}.js`);
// 或者使用条件导入
const module = await (condition ?
import('./moduleA.js') :
import('./moduleB.js')
);
- 资源加载
// 预加载关键资源
const criticalData = await fetch('/api/critical-data').then(r => r.json());
const heavyLibrary = await import('/libs/heavy-library.js');
export function useApp() {
return { criticalData, heavyLibrary };
}
- 数据库连接
// db.js
import { connect } from './database.js';
const connection = await connect({
host: 'localhost',
database: 'myapp'
});
export { connection };
实际示例
示例 1:API 数据预加载
// data-loader.js
const API_BASE = 'https://api.example.com';
// 并行加载多个数据
const [users, products, settings] = await Promise.all([
fetch(`${API_BASE}/users`).then(r => r.json()),
fetch(`${API_BASE}/products`).then(r => r.json()),
fetch(`${API_BASE}/settings`).then(r => r.json())
]);
export { users, products, settings };
示例 2:功能检测和回退
// feature-detection.js
let imageProcessor;
try {
// 尝试加载 WebAssembly 版本
imageProcessor = await import('./wasm-image-processor.js');
} catch {
// 回退到 JavaScript 版本
imageProcessor = await import('./js-image-processor.js');
}
export { imageProcessor };
示例 3:应用初始化
// app.js
import { auth } from './auth.js';
// 等待用户认证
const user = await auth.getCurrentUser();
if (!user) {
// 如果未登录,重定向到登录页
window.location.href = '/login';
} else {
// 加载主应用
const app = await import('./main-app.js');
app.initialize(user);
}
错误处理
- 使用 try-catch
try {
const data = await fetch('/api/data').then(r => r.json());
console.log('Data loaded:', data);
} catch (error) {
console.error('Failed to load data:', error);
// 提供回退数据
const fallbackData = { /* ... */ };
}
- 使用 .catch()
const data = await fetch('/api/data')
.then(r => r.json())
.catch(error => {
console.error('Failed to load:', error);
return getFallbackData();
});
- 模块级别的错误处理
// safe-import.js
let importantModule;
try {
importantModule = await import('./important-module.js');
} catch (error) {
console.warn('Using fallback implementation');
importantModule = await import('./fallback-module.js');
}
export default importantModule;
与模块系统的关系
导出异步值
// 导出 Promise
export const dataPromise = fetch('/api/data').then(r => r.json());
// 使用顶层 await 导出实际值
export const data = await fetch('/api/data').then(r => r.json());
依赖关系
// moduleA.js
export const config = await loadConfig();
// moduleB.js - 会等待 moduleA 的 await 完成
import { config } from './moduleA.js';
console.log(config); // 这里 config 已经是解析后的值
最佳实践
- 谨慎使用
// ✅ 好的使用 - 必要的初始化
const criticalConfig = await loadConfig();
// ❌ 避免不必要的阻塞
// const nonCriticalData = await fetch('/api/non-critical');
- 并行加载
// 顺序加载(较慢)
// const a = await loadA();
// const b = await loadB();
// 并行加载(推荐)
const [a, b] = await Promise.all([loadA(), loadB()]);
- 提供加载状态
// 对于长时间运行的初始化
console.log('Loading application...');
const app = await import('./heavy-app.js');
console.log('Application loaded!');
- 错误边界
// 提供优雅的降级方案
let feature;
try {
feature = await import('./advanced-feature.js');
} catch {
feature = { use: () => console.log('Feature not available') };
}
注意事项:
1、阻塞效应:顶层 await 会阻塞模块的解析和依赖模块的执行
2、循环依赖:要小心由顶层 await 引起的循环依赖问题
3、服务器端渲染:在 SSR 环境中要特别注意异步模块的加载
4、树摇优化:某些打包工具可能对顶层 await 的支持有限
顶层 await 大大简化了异步模块初始化的代码,让 JavaScript 模块系统更加强大和灵活。
更多推荐
所有评论(0)