diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc2739f..f9a28f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ All notable changes to `hy-event-store` are documented in this file.
- Add first-party TypeScript declarations for event payloads, state keys,
actions, and action return values, plus compile-time regression coverage.
+- Allow off to cancel a pending once listener by using
+ the original callback reference.
## 1.4.0 - 2026-08-19
diff --git a/README.md b/README.md
index eaf1083..f29436f 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,7 @@ store.offStates(["count", "status"], renderProfile)
| on(eventName, callback, thisArg?) | Register a listener. | The event bus |
| once(eventName, callback, thisArg?) | Register a listener removed before its first callback runs. | The event bus |
| emit(eventName, ...payload) | Notify every listener for an event. | The event bus |
-| off(eventName, callback) | Remove registrations for the exact callback. It is safe when the event has no listeners. | The event bus |
+| off(eventName, callback) | Remove registrations for the callback passed to on or once. It is safe when the event has no listeners. | The event bus |
| clear() | Remove listeners for every event. | The event bus |
| hasEvent(eventName) | Check whether an event has at least one listener. | Boolean |
diff --git a/README.zh-CN.md b/README.zh-CN.md
index d66e3fb..2851ba3 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -150,7 +150,7 @@ store.offStates(["count", "status"], renderProfile)
| on(eventName, callback, thisArg?) | 注册监听器。 | 事件总线实例 |
| once(eventName, callback, thisArg?) | 注册仅在第一次触发时执行的监听器。 | 事件总线实例 |
| emit(eventName, ...payload) | 通知某个事件的全部监听器。 | 事件总线实例 |
-| off(eventName, callback) | 移除指定回调的监听;事件不存在时也可安全调用。 | 事件总线实例 |
+| off(eventName, callback) | 移除通过 on 或 once 注册的回调;事件不存在时也可安全调用。 | 事件总线实例 |
| clear() | 清除全部事件的监听器。 | 事件总线实例 |
| hasEvent(eventName) | 判断事件是否至少有一个监听器。 | 布尔值 |
diff --git a/Test/event-bus.test.js b/Test/event-bus.test.js
index e116f54..d07a81b 100644
--- a/Test/event-bus.test.js
+++ b/Test/event-bus.test.js
@@ -29,6 +29,21 @@ test("once does not skip the next handler", () => {
assert.deepEqual(calls, ["once", "always", "always"])
})
+test("off cancels a once listener by its original callback", () => {
+ const bus = new HYEventBus()
+ let calls = 0
+ const callback = () => {
+ calls += 1
+ }
+
+ bus.once("update", callback)
+ bus.off("update", callback)
+ bus.emit("update")
+
+ assert.equal(calls, 0)
+ assert.equal(bus.hasEvent("update"), false)
+})
+
test("off is safe for missing events and removes matching handlers", () => {
const bus = new HYEventBus()
const callback = () => {}
diff --git a/src/event-bus.js b/src/event-bus.js
index 6a09670..a5fe1c7 100644
--- a/src/event-bus.js
+++ b/src/event-bus.js
@@ -20,6 +20,7 @@ class HYEventBus {
handlers.push({
eventCallback,
+ originalCallback: eventCallback,
thisArg
})
return this
@@ -39,7 +40,9 @@ class HYEventBus {
eventCallback.apply(thisArg, payload)
}
- return this.on(eventName, tempCallback, thisArg)
+ this.on(eventName, tempCallback, thisArg)
+ this.eventBus[eventName][this.eventBus[eventName].length - 1].originalCallback = eventCallback
+ return this
}
emit(eventName, ...payload) {
@@ -70,7 +73,7 @@ class HYEventBus {
}
this.eventBus[eventName] = handlers.filter(handler => {
- return handler.eventCallback !== eventCallback
+ return handler.eventCallback !== eventCallback && handler.originalCallback !== eventCallback
})
if (this.eventBus[eventName].length === 0) {