第 3 卷
6.map(hook => hook.callback.bind(thisArg))// 转成可调用的回调数组 7}
dispatch() 只做一件事:根据事件名,从 _hooks 里取出监听器列表,过滤后返回。五种模式的第一步都是调用它,拿到同一个列表。
真正的差异,全在「拿到列表之后怎么遍历」。看五种模式的实现,它们各自只有一行核心(node_modules/@deepseek-ai/cordis/src/events.ts):
复制代码 1// ① emit:同步 map,逐个调用,不管返回值
2emit(...args) { this.dispatch('emit', args).map(cb => cb(...args)) } 3
| 4 | //②parallel:并发map,等所有结果(有错就汇总成AggregateError) |
|---|---|
| 5 | asyncparallel(...args) {* |
| 6 | constresults =awaitPromise.allSettled(this.dispatch('emit', args).map(async |
| 7 | ... |
| 8 | } |
9
| 10 | //③serial:顺序for,逐个await,一旦isBailed就停下并返回 |
|---|---|
| 11 | asyncserial(...args) { |
| 12 | for(constcbofthis.dispatch('serial', args)) { |
| 13 | constresult =awaitcb(...args) |
| 14 | if(isBailed(result))returnresult |
| 15 | } |
| 16 | } |
17
| 18 | //④bail:serial的同步版,同样的for + isBailed,只是不await |
|---|---|
| 19 | bail(...args) { |
| 20 | for(constcbofthis.dispatch('bail', args)) { |
| 21 | constresult =cb(...args) |
| 22 | if(isBailed(result))returnresult |
| 23 | } |
| 24 | } |
| 26 | //⑤waterfall:shift递归,每层拿next往下钻,形成洋葱 |
| 27 | waterfall(...args) { |
把五种实现摆在一起,一个深刻的统一就浮现了:
「拿到监听器列表」这件事,五种模式完全一样——都是 dispatch();
「遍历列表」的策略不同,才造就了五种语义:
emit = 同步 map(不管结果)
parallel = Promise.allSettled(并发等待)
serial / bail = for + isBailed 短路(一个 await、一个不 await)
waterfall = shift 递归(类似多层的洋葱)
顺便提一句:serial/bail 里的 isBailed(result),就是之前说的那个判定—— value !== null && value !== false && value !== undefined。返回非空值
就「返回」,正是靠这一行。
模式回顾
五种模式都讲完了,我们回头用一张表 + 一张图,把它们整体收拢一下:

下次写事件时,你沿着这张快速决策图逐层问自己三个问题,答案自然就出来了:
复制代码 1要不要拿返回值?
2├── 不要 → 要不要等它们做完?
3│├── 不要 → emit(同步广播)
4│└── 要→ parallel(并发等待)
5└── 要 → 什么语义?
6├── "谁能处理谁接单"(竞争)→ 同步用 bail / 异步用 serial 7└── "层层加工/可短路"(环绕中间件)→ waterfall
本讲小结
今天这一讲,我们补齐了 Cordis 世界观的第三块通信拼图。
我们学会了五种分发模式:emit 同步广播、parallel 并发等待、serial 异步竞争、 bail 同步竞争、waterfall 洋葱式中间件。它们按“是否等待、是否并发、有无返回值”等
多个维度各司其职。