顶层 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);

使用场景

  1. 模块初始化
// config.js
const config = await fetch('/api/config').then(r => r.json());
export { config };

// 在其他模块中使用
import { config } from './config.js';
console.log(config.apiUrl);
  1. 动态导入
// 根据条件动态导入模块
const userLocale = navigator.language;
const messages = await import(`./locales/${userLocale}.js`);

// 或者使用条件导入
const module = await (condition ? 
  import('./moduleA.js') : 
  import('./moduleB.js')
);
  1. 资源加载
// 预加载关键资源
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 };
}
  1. 数据库连接
// 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);
}

错误处理

  1. 使用 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 = { /* ... */ };
}
  1. 使用 .catch()
const data = await fetch('/api/data')
  .then(r => r.json())
  .catch(error => {
    console.error('Failed to load:', error);
    return getFallbackData();
  });
  1. 模块级别的错误处理
// 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 已经是解析后的值

最佳实践

  1. 谨慎使用
// ✅ 好的使用 - 必要的初始化
const criticalConfig = await loadConfig();

// ❌ 避免不必要的阻塞
// const nonCriticalData = await fetch('/api/non-critical');
  1. 并行加载
// 顺序加载(较慢)
// const a = await loadA();
// const b = await loadB();

// 并行加载(推荐)
const [a, b] = await Promise.all([loadA(), loadB()]);
  1. 提供加载状态
// 对于长时间运行的初始化
console.log('Loading application...');
const app = await import('./heavy-app.js');
console.log('Application loaded!');
  1. 错误边界
// 提供优雅的降级方案
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 模块系统更加强大和灵活。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐