async/await任务依赖框架核心是构建有向无环图并按拓扑序调度:任务含id、async run函数和字符串依赖数组;执行器校验环后,并发触发入度为0任务,支持超时、重试与上下文透传。

用 async/await 实现任务依赖框架,核心是把“谁等谁”变成可描述的图结构,并让执行器按拓扑序调度异步任务。不需复杂库,几段清晰逻辑就能支撑常见场景(如构建流程、数据管道、UI 加载链)。
定义带依赖的任务单元
每个任务应携带 ID、执行函数和前置依赖列表。关键点是:任务体必须是 async 函数,返回 Promise;依赖项用 ID 字符串表示,而非直接引用函数,便于解耦和动态组装。
- 任务对象示例:{ id: 'fetch-user', run: async () => { /* await fetch(...) */ }, depends: ['init-api'] }
- 依赖字段 depends 是字符串数组,支持跨模块声明,避免闭包强绑定
- 允许空依赖(即无前置),也支持多依赖(如 ['auth', 'config']),执行器后续按序等待全部完成
构建有向无环图(DAG)并检测循环
依赖关系本质是图。先将所有任务按 ID 索引,再遍历每个任务的 depends,建立邻接表。接着用 DFS 或三色标记法判断是否存在环——若发现环,立即抛错,因为 async/await 无法自动打破死锁。
- 简单检测逻辑:对每个未访问节点启动 DFS,递归中若遇到「正在访问」状态的节点,即存在环
- 建议在初始化阶段校验,而非运行时,提升错误可见性
- 不强制要求全图连通;孤立任务(无依赖也无被依赖)可独立执行
按拓扑序并发安全地执行
拓扑排序确保每个任务只在其所有依赖完成后才开始。实际执行时,可用队列 + Promise.allSettled 驱动:每次取出所有入度为 0 的任务并发触发,待它们全部 settle 后,更新剩余节点入度,继续下一轮。
- 避免用 for...of + await 串行执行——那会丢失并发收益
- 推荐用 Promise.allSettled(tasks.map(t => t.run())) 批量触发,保留各任务的成败独立性
- 失败任务默认中断后续(可配 failFast: false 实现容错模式)
提供实用扩展能力
真实场景需要超时控制、重试、上下文透传等。这些不应侵入核心调度逻辑,而应通过包装 run 函数实现:
- 超时:用 Promise.race([task(), new Promise((_, r) => setTimeout(r, ms))])
- 重试:封装为高阶函数 withRetry(asyncFn, { times: 3 })
- 上下文:任务 run 函数接收一个 context 对象,由框架在调用时注入(如共享缓存、日志实例)











