推荐导出异步工厂函数或预初始化promise:前者支持按需调用与重试,后者适合单例场景;禁止在export右侧直接使用await,因语法不支持。

在 JavaScript 模块中,async/await 不能直接用于 export default 或 export const 的顶层(因为模块顶层不支持 await),但你可以通过导出一个**异步工厂函数**或一个**预初始化的 Promise 实例**来安全返回异步创建的对象。核心思路是:不导出“等待后的值”,而是导出“可等待的承诺”或“能产生承诺的函数”。
✅ 推荐方式:导出一个异步初始化函数
这是最清晰、最可控、最符合模块语义的做法。调用方明确知道需要 await,且可按需多次调用(如需重试或不同配置)。
// db.js
import { createConnection } from 'mysql2/promise';
export async function initDB() {
const connection = await createConnection({
host: 'localhost',
user: 'root',
database: 'test'
});
return {
query: (sql) => connection.execute(sql),
close: () => connection.end()
};
}
使用时:
// app.js
import { initDB } from './db.js';
const db = await initDB(); // ✅ 在顶层 await(ESM 支持)
db.query('SELECT 1');
✅ 可选方式:导出一个已 resolve 的 Promise(单例模式)
适合只需要一份全局实例、且初始化只需一次的场景(如配置加载、客户端连接)。模块首次导入时触发初始化,后续导入复用同一实例。
// client.js
import { createClient } from 'redis';
const clientPromise = createClient().then(c => {
c.on('error', console.error);
return c;
});
// 导出 Promise,不是实例本身
export const redisClient = clientPromise;
// 也可额外导出便捷的 await 封装(可选)
export async function getRedisClient() {
return redisClient;
}
使用时:
// service.js
import { redisClient } from './client.js';
// 方式一:直接 await 导出的 Promise
const client = await redisClient;
// 方式二:用辅助函数(语义更清晰)
// const client = await getRedisClient();
client.set('key', 'value');
⚠️ 不推荐:试图在顶层 await 后直接 export(语法错误)
以下写法会报错:await is only valid in async functions and the top level bodies of modules —— 注意:虽然顶层 await 在 ESM 中是合法的,但它**不能出现在 export 声明右侧的表达式中**。
// ❌ 错误示例:语法不允许 export const instance = await someAsyncInit(); // SyntaxError // ❌ 也不允许这样 const inst = await someAsyncInit(); export default inst; // 同样报错:顶层 await 不能和 export 语句混用(部分环境行为不一致,应避免)
? 补充建议:配合默认导出与命名导出提升可用性
可以同时提供函数式接口和 Promise 式接口,兼顾灵活性与简洁性:
// logger.js
import winston from 'winston';
async function createLogger() {
const transport = new winston.transports.File({ filename: 'app.log' });
return winston.createLogger({ transports: [transport] });
}
// 默认导出异步工厂(推荐主入口)
export default createLogger;
// 命名导出预初始化 Promise(供需要单例的场景)
export const logger = createLogger();
// 命名导出同步占位符(可选,避免未 await 就调用)
export const getLogger = () => logger;
这样使用者既可以用 import createLogger from './logger.js'; const log = await createLogger();,也可以用 import { logger } from './logger.js'; const log = await logger;,语义明确,无歧义。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











