PGlite 如何用 .listen() 与 NOTIFY 实现数据库内频道通知?
【免费下载链接】pgliteEmbeddable Postgres with real-time, reactive bindings.项目地址: https://gitcode.com/GitHub_Trending/pg/pglite
在 PGlite 中实现「某个事件发生后,JavaScript 端立刻收到一条消息」这类需求,不需要自己轮询数据表:Postgres 原生的LISTEN/NOTIFY机制在 PGlite 里被封装成了.listen()方法。执行NOTIFY语句后,订阅了对应频道的回调函数会收到 payload 字符串。这套 API 在 Node、Bun、Deno 和浏览器中都可用(PGlite 同时支持这两个运行环境),本文走一遍从安装、订阅频道、发送通知到取消订阅的完整路径,以及如何验证通知确实到达了回调。
准备条件:安装并创建实例
先用包管理器安装 PGlite(以 npm 为例,其他包管理器见 Getting started):
npm install @electric-sql/pglite创建实例。不带参数时是内存数据库,适合演示和开发:
import { PGlite } from '@electric-sql/pglite' const pg = new PGlite()如果需要持久化,dataDir支持不同存储后端(详见 PGlite API 文档):
file://或不加前缀:文件系统存储,Node 和 Bun 可用;idb://:IndexedDB 存储,浏览器可用;memory://:内存临时存储,全平台可用。
例如浏览器中持久化到 IndexedDB:new PGlite('idb://my-pgdata')。
也可以改用静态方法await PGlite.create(options),它会额外等待.waitReady的 Promise,确保数据库完全初始化后再返回实例。不过查询方法本身在数据库未就绪时会自动等待waitReady完成,所以一般不必手动等待。
主路径:用 .listen() 订阅频道并接收 NOTIFY
.listen()的签名(来自 API 文档):
.listen(channel: string, callback: (payload: string) => void): Promise<() => Promise<void>>它订阅一个pg_notify频道,回调接收通知的 payload(字符串),并返回一个取消订阅函数。.listen()会自动处理订阅,这一点与后文的onNotification不同。
一条最短的完整示例,可以直接在 Node 中运行:
import { PGlite } from '@electric-sql/pglite' const pg = new PGlite() // 订阅频道 'test',回调接收 payload const unsub = await pg.listen('test', (payload) => { console.log('Received:', payload) }) // 向频道 'test' 发送一条带 payload 的通知 await pg.query("NOTIFY test, 'Hello, world!'") // 确认收到通知后取消订阅 await unsub() // 干净地关闭数据库 await pg.close()回调是异步被触发的:NOTIFY语句执行完成后,通知会经由 Postgres 通知机制到达回调,而不是同步在query返回的同时调用。官方浏览器示例 notify.html 因此在每步操作之间显式等待了 500ms,用于让日志按顺序展示。
官方浏览器示例的完整流程值得注意最后一步:
await unsub() await pg.query("NOTIFY test, 'Will not be received!'")取消订阅后再NOTIFY,这条通知不会到达回调——这是判断取消订阅是否生效的直接依据。
验证通知是否到达
三个层级的验证方式都在仓库里:
- 控制台输出:示例中的
console.log('Received:', payload)会打印收到的 payload,文档示例输出为Received: Hello, world!(文档示例,实际 payload 取决于你NOTIFY传入的内容)。 - 官方测试断言:notify.test.ts 中的
notify用例订阅频道后执行await pg.exec("NOTIFY test, '321'"),并断言回调收到的payload严格等于'321';unlisten用例则断言取消订阅后再NOTIFY不会触发回调。 - 浏览器示例页面:examples 列表 中的 "Notify and Listen" 对应 packages/pglite/examples/notify.html,页面会输出收到的消息日志,可以直观看到订阅期内收到两条消息、取消订阅后的那条没有到达。
取消订阅的两种方式
listen返回的 unsubscribe 函数:await unsub()只移除当前这个回调。.unlisten(channel, callback?):提供callback时只移除该回调;不提供时移除该频道上的所有回调。签名:.unlisten(channel: string, callback?: (payload: string) => void): Promise<void>。
一个回调可能多次调用.listen()订阅同一频道(每次调用都会追加一个订阅),此时用带callback参数的.unlisten()精确移除,而不是清掉整个频道。
替代路径:onNotification 接收所有频道的通知
如果不想按频道逐个注册回调,可以用onNotification挂一个全局处理器,它会收到 Postgres 发来的所有通知,回调签名是(channel: string, payload: string) => void:
pg.onNotification((channel, payload) => { console.log(channel, payload) }) await pg.exec('LISTEN test') await pg.exec("NOTIFY test, '123'")API 文档明确警告:onNotification只添加事件处理器,并不执行订阅,必须自己先用LISTEN channel_name订阅频道,通知才会到达。这与.listen()(内部自动完成订阅)是主要区别。不再需要时用offNotification(callback)移除对应处理器。
频道命名规则与易错点
.listen()的频道名遵循 Postgres 的标识符规则,这也是官方测试专门覆盖的行为(见 notify.test.ts 的 "check notify case sensitivity + special chars as Postgresql" 用例):
- 不加双引号时频道名会被转为小写:
pg.listen('TeStiNG')等价于pg.listen('testing'),NOTIFY PostgresDefaultLower与NOTIFY postgresdefaultlower命中的是同一频道。 - 加双引号时区分大小写:
pg.listen('"TeST"')与pg.listen('test')是不同频道;大小写不同的双引号频道名之间不会互相收到通知。 - 含空格或特殊字符的频道名必须加双引号:
pg.listen('"Quoted Channel With Spaces"')和pg.listen('"test&me"')可以正常订阅;而pg.listen('Unquoted Channel With Spaces')或pg.listen('test&me')(不带引号)会直接抛错,对应的NOTIFY语句同样会失败。
如果你的业务里频道名来自变量,建议统一用双引号包裹并保证LISTEN/NOTIFY两端引号用法一致,避免因为隐式小写化或非法字符导致「发了通知但收不到」。
小结与延伸
- 主路径是
.listen(channel, callback)订阅 +NOTIFY channel, 'payload'发送,取消订阅用返回的unsub()或.unlisten();验证依据是回调收到的 payload 与你NOTIFY的内容一致、取消订阅后不再收到。 - 需要跨频道统一处理时用
onNotification,但记得它不替你LISTEN。 - 更完整的接口说明见 PGlite API 文档,可直接运行的浏览器示例见 packages/pglite/examples/notify.html,行为断言见 packages/pglite/tests/notify.test.ts。
【免费下载链接】pgliteEmbeddable Postgres with real-time, reactive bindings.项目地址: https://gitcode.com/GitHub_Trending/pg/pglite
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考