Skip to content

Cordis 入门 03:用事件让插件通信

插件之间不能总靠直接调用。直接调用会让插件互相知道太多细节,也会让卸载变麻烦。Cordis 提供了事件系统,让插件可以通过事件协作。

这一篇我们练 ctx.on()ctx.emit()。你会看到,事件监听器不是裸挂在全局对象上,而是归属于当前插件的 Fiber。插件卸载时,监听器会一起消失。

这篇要练什么

本文会实现一个最小消息事件:

text
sender plugin -> ctx.emit('demo/message') -> listener plugin

你会掌握:

  • ctx.on() 注册监听器。
  • ctx.emit() 触发事件。
  • 理解监听器为什么会随插件卸载。
  • 用事件拆开插件之间的直接依赖。

最小事件例子

index.mjs 改成下面这样:

js
import { Context } from 'cordis'

const ctx = new Context()

function listenerPlugin(ctx) {
  ctx.on('demo/message', (text) => {
    console.log('received:', text)
  })
}

await ctx.plugin(listenerPlugin)

ctx.emit('demo/message', 'hello event')

运行:

bash
node index.mjs

输出:

text
received: hello event

这里有两个动作:

  • ctx.on('demo/message', listener) 注册监听器。
  • ctx.emit('demo/message', 'hello event') 触发事件。

事件名可以是字符串。入门阶段建议使用带命名空间的事件名,例如 demo/message,这样不容易和别的插件冲突。

验证监听器会被清理

继续改代码:

js
import { Context } from 'cordis'

const ctx = new Context()

function listenerPlugin(ctx) {
  ctx.on('demo/message', (text) => {
    console.log('received:', text)
  })
}

const fiber = ctx.plugin(listenerPlugin)
await fiber

ctx.emit('demo/message', 'before dispose')

await fiber.dispose()

ctx.emit('demo/message', 'after dispose')

输出:

text
received: before dispose

第二次事件没有输出。原因是插件卸载时,Cordis 自动移除了这个插件注册的监听器。

这就是 Cordis 事件系统和普通事件总线的重要差别。Cordis 的监听器被生命周期接管了。

写一个 sender 插件

现在把触发事件的逻辑也放进插件。

js
import { Context } from 'cordis'

const ctx = new Context()

function listenerPlugin(ctx) {
  ctx.on('demo/message', (text) => {
    console.log('listener received:', text)
  })
}

function senderPlugin(ctx) {
  ctx.emit('demo/message', 'message from sender')
}

await ctx.plugin(listenerPlugin)
await ctx.plugin(senderPlugin)

输出:

text
listener received: message from sender

这里 senderPlugin 不需要知道 listenerPlugin 的存在。它只负责发事件。谁要处理这个事件,由其他插件决定。

这种方式适合这些场景:

  • 插件启动后广播一个状态。
  • 一个插件产生消息,多个插件分别处理。
  • 业务动作需要被日志、指标、通知插件同时观察。
  • 插件之间只共享事件协议,不共享实现细节。

小练习:多个监听器

把监听器拆成两个插件:

js
import { Context } from 'cordis'

const ctx = new Context()

function consoleLogger(ctx) {
  ctx.on('demo/message', (text) => {
    console.log('[console]', text)
  })
}

function auditLogger(ctx) {
  ctx.on('demo/message', (text) => {
    console.log('[audit]', {
      event: 'demo/message',
      text,
    })
  })
}

function senderPlugin(ctx) {
  ctx.emit('demo/message', 'hello plugins')
}

await ctx.plugin(consoleLogger)
await ctx.plugin(auditLogger)
await ctx.plugin(senderPlugin)

输出:

text
[console] hello plugins
[audit] { event: 'demo/message', text: 'hello plugins' }

这个例子体现了事件系统的价值:发送方不需要关心有几个接收方。

emit、serial 和 waterfall 怎么选

入门阶段先重点掌握 emit。以后你会遇到更多分发方式。

API适合场景
ctx.emit()同步广播事件,不等待异步结果
ctx.parallel()并行执行监听器,并等待全部完成
ctx.serial()按顺序执行监听器,适合需要顺序控制的流程
ctx.bail()遇到有效返回值就停止
ctx.waterfall()洋葱式 next 流程,适合内部拦截链

本文先不展开这些高级模式。新手只要先会用 on + emit,就已经能写很多插件通信场景。

入门检查点

事件系统要记住两句话:

text
插件之间可以通过事件通信
事件监听器会跟随当前插件生命周期自动清理

下一篇我们进入 Cordis 更有特点的部分:服务和依赖。事件解决的是“发一条消息给谁都行”,服务解决的是“我明确依赖某个能力”。


系列导航