parallel / pipeline 失败语义 深度解析
从 deer-workflow 源码逐段确认该模块的契约、执行路径、事件语义和失败边界。
导入链与契约
parallel / pipeline 失败语义 的第一层证据来自 src/flow/parallel.ts:22、src/flow/parallel.ts:25、src/flow/parallel.ts:28、src/flow/parallel.ts:30 与 src/flow/types.ts:20:这些行把入口、上下文、错误或配置集中在一个小范围内,说明 deer-workflow 倾向把复杂度留在明确契约里,而不是散到调用者脚本。这里的设计价值是让 Agent 自动化可以被测试、被日志系统消费,也能被 CLI 和库调用共享。
22export async function parallel<const TTasks extends readonly ParallelTask[]>( 23 tasks: TTasks, 24): Promise<ParallelResults<TTasks>> { 25 const results = await Promise.all( 26 Array.from(tasks, async (task) => { 27 try { 28 return await task(); 29 } catch { 30 return null; 31 } 32 }), 33 ); 34 35 return results as ParallelResults<TTasks>; 36}
核心实现,逐段拆
▸入口与状态
第二层证据是 src/flow/pipeline.ts:78、src/flow/pipeline.ts:82、src/flow/pipeline.ts:87、src/flow/pipeline.ts:91 与 src/flow/types.ts:38。这些引用展示了失败路径、事件路径或参数路径如何闭环:不是只在 happy path 发一个结果,而是把输入解析、阶段切换、stdout/stderr、schema 或 duration 都纳入可解释的运行轨迹。
27export function pipeline<TOriginal>( 28 items: readonly TOriginal[], 29): Promise<Array<TOriginal | null>>; 30 31export function pipeline<TOriginal, TStage1>( 32 items: readonly TOriginal[], 33 stage1: PipelineStage<TOriginal, TOriginal, TStage1>, 34): Promise<Array<Awaited<TStage1> | null>>; 35 36export function pipeline<TOriginal, TStage1, TStage2>( 37 items: readonly TOriginal[], 38 stage1: PipelineStage<TOriginal, TOriginal, TStage1>, 39 stage2: PipelineStage<Awaited<TStage1>, TOriginal, TStage2>, 40): Promise<Array<Awaited<TStage2> | null>>; 41 42export function pipeline<TOriginal, TStage1, TStage2, TStage3>( 43 items: readonly TOriginal[], 44 stage1: PipelineStage<TOriginal, TOriginal, TStage1>, 45 stage2: PipelineStage<Awaited<TStage1>, TOriginal, TStage2>, 46 stage3: PipelineStage<Awaited<TStage2>, TOriginal, TStage3>, 47): Promise<Array<Awaited<TStage3> | null>>; 48 49export function pipeline<TOriginal, TStage1, TStage2, TStage3, TStage4>( 50 items: readonly TOriginal[], 51 stage1: PipelineStage<TOriginal, TOriginal, TStage1>, 52 stage2: PipelineStage<Awaited<TStage1>, TOriginal, TStage2>, 53 stage3: PipelineStage<Awaited<TStage2>, TOriginal, TStage3>, 54 stage4: PipelineStage<Awaited<TStage3>, TOriginal, TStage4>, 55): Promise<Array<Awaited<TStage4> | null>>; 56 57export function pipeline< 58 TOriginal, 59 TStage1, 60 TStage2, 61 TStage3, 62 TStage4, 63 TStage5, 64>( 65 items: readonly TOriginal[], 66 stage1: PipelineStage<TOriginal, TOriginal, TStage1>, 67 stage2: PipelineStage<Awaited<TStage1>, TOriginal, TStage2>, 68 stage3: PipelineStage<Awaited<TStage2>, TOriginal, TStage3>, 69 stage4: PipelineStage<Awaited<TStage3>, TOriginal, TStage4>, 70 stage5: PipelineStage<Awaited<TStage4>, TOriginal, TStage5>, 71): Promise<Array<Awaited<TStage5> | null>>; 72 73export function pipeline<TOriginal>( 74 items: readonly TOriginal[], 75 ...stages: RuntimePipelineStage<TOriginal>[] 76): Promise<Array<unknown | null>>;
▸失败与边界
第三层证据来自 tests/flow/pipeline.test.ts:6、tests/flow/pipeline.test.ts:16、tests/flow/pipeline.test.ts:39、tests/flow/pipeline.test.ts:56 与 src/flow/types.ts:49。这些行说明这个模块的边界主要靠类型和测试守住:当 workflow 嵌套、metadata、JSON input、CLI 命令或 Agent 子进程出现异常时,源码会抛出具名错误或保留原始诊断,而不是吞掉上下文。
78export async function pipeline<TOriginal>( 79 items: readonly TOriginal[], 80 ...stages: RuntimePipelineStage<TOriginal>[] 81): Promise<Array<unknown | null>> { 82 return Promise.all( 83 Array.from(items, async (original, index) => { 84 let value: unknown = original; 85 86 try { 87 for (const stage of stages) { 88 value = await stage(value as never, original, index); 89 } 90 return value; 91 } catch { 92 return null; 93 } 94 }), 95 ); 96}
关键流程
▸端到端调用路径
最后看 src/flow/pipeline.ts:31、src/flow/pipeline.ts:57、src/flow/pipeline.ts:73、src/flow/parallel.ts:35 与 tests/flow/pipeline.test.ts:60。这组证据把实现和测试连起来:源码定义行为,测试断言事件顺序、错误闭合和返回值结构。对真实 coding-agent 平台来说,这比 README 示例更有价值,因为它回答了失败时系统还剩下哪些可观察信号。
边界 / 失败模式
- 源码把错误路径显式化,但调用方仍要决定 null、throw 和 retry 的业务语义。
- CLI adapter 依赖宿主机安装对应命令,测试中通过 stub 覆盖,生产中还需要版本治理。
- 事件流是同步 emitter,监听器自身不能做阻塞重活。