Top-level await仅触发初始化,不提供连接感知能力;真正的连接感知需封装带状态的Promise工厂函数,导出可调用、可监听、可重试的createDB()及isReady()、healthCheck()等接口,并避免循环依赖。
Top-level await 本身不提供“连接感知”能力,它只是让模块能等待异步操作完成后再导出符号。真正的连接感知需要你主动设计状态检查、重试机制和就绪信号——不是靠 await 自动实现,而是用 await 搭配可观察的状态封装来达成。
避免在顶层直接写 const db = await connect(),这会让模块变成“黑盒”,调用方无法知道连接是否失败、是否重试中、是否已断开。应该导出一个函数,每次调用都返回一个新的、可监听状态的初始化 Promise:
async function createDB() 封装连接逻辑,内部处理重试、超时、错误分类let instance = null + 双重检查缓存,实现首次调用初始化、后续复用仅靠 await 完成一次连接不够,生产环境需要持续感知连接有效性。可在工厂函数返回的对象上挂载状态方法:
db.isReady():返回布尔值,基于当前实例是否存在且未标记为断开db.healthCheck():发起轻量探测(如 SELECT 1),返回 Promise
db.on('disconnect', handler):暴露事件(可用 EventEmitter 或自定义事件总线)这样其他模块就能在运行时主动轮询或监听状态,而不是假设“导入即可用”。
若确实需要模块加载时就建立连接(例如配置驱动型服务),可保留顶层 await,但必须确保它只触发初始化、不阻塞符号导出:
createDB、isReady 等工具函数await createDB().catch(() => {}) 静默触发初始化(不 throw,避免模块加载失败)ready Promise:export const ready = createDB().catch(err => { console.warn('DB init failed, will retry on first use', err); })这样既利用了顶层 await 的启动便利性,又把控制权交还给调用方——它可以选择 await db.ready 等待,也可以跳过直接调用 createDB() 并自己处理错误。
数据库模块常被 config、auth、logger 等多处引用,极易因 top-level await 引发 A→B→A 类死锁。防范方式很具体:
config.mjs 或 auth.mjs 中直接 await 数据库连接getDB()),不依赖模块级初始化顺序node --trace-module-loading 启动验证加载链,确认 db 模块不处于循环路径的关键节点本质上,连接感知 ≠ 一次性 await 成功,而是把连接从“静态资源”变成“可观察服务”。顶层 await 是启动扳机,不是状态管理器。