异步模块应按功能边界拆分为网络请求、业务编排、数据转换三类,错误需分层定义:底层用具体技术错误,中间层转为领域错误,顶层统一为用户友好响应,并通过显式错误声明、可观测性钩子和统一错误构造器保障协作与追踪。

异步函数逻辑的模块化拆分与错误分层,核心在于让每个模块职责清晰、边界明确,同时让错误在不同层级表达恰当语义——底层暴露细节,上层聚焦业务,顶层面向用户或系统。
按功能边界拆分异步模块
每个模块只封装一类可独立验证的异步行为,输入输出明确,不掺杂其他职责。
- 网络请求类(如 fetchUser、submitOrder):只负责调用 API 并返回原始响应或结构化数据,不处理业务规则
- 业务编排类(如 createUserProfile):组合多个底层调用,做参数组装、顺序控制、条件分支,但不直接操作 I/O
- 数据转换/校验类(如 validateEmailAsync、normalizeAddress):纯逻辑,可同步也可异步,便于单元测试
错误类型按层级定义与转换
避免所有地方都 throw new Error('xxx'),而是让错误随调用链向上“提纯”。
- 底层模块(I/O 层):使用具体错误类型,如 AbortError、NetworkError、sqlite3.SqliteError,保留堆栈和原始状态码
- 中间模块(服务层):用领域错误枚举或类封装,例如 UserNotFoundError 或 PaymentDeclinedError,通过 map_err(Rust)、catch + throw new XXXError()(JS)完成转换
- 顶层模块(API/CLI/UI):不暴露技术细节,统一转为 HTTP 状态码 + 友好 message,必要时附带 traceId 供排查
模块间协作需显式声明错误归属
一个 await 就是一个潜在的失败点,不能让它“裸奔”。模块化不是把代码切开就完事,关键是协作契约清晰。
- 每个导出的异步函数,文档或类型签名中应注明可能抛出的错误类型(如 JSDoc @throws 或 TypeScript 的 throws 声明)
- 调用方必须决定如何响应该错误:重试(如网络抖动)、降级(如返回缓存)、中断流程(如权限不足)、或包装后继续上抛
- 避免“吞错”写法:await fetch(...).catch(() => {}) 是反模式;应捕获后明确处理,哪怕只是记录
模块化不等于隔离,需预留可观测性接口
模块拆得再干净,运行时仍需追踪错误来源。建议在关键模块入口/出口注入轻量日志或指标钩子。
- 例如在 userModule() 开始前打点 log.start('userModule', { userId }),异常时自动关联上下文
- 使用统一的错误构造器(如 createAppError(code, message, cause)),确保所有模块抛出的错误都带 code、timestamp、sourceModule 字段
- 模块测试时,能单独 mock 某个子模块并验证它在特定错误下是否触发了预期的上层行为











