Add signal-support in the EventBus, and utilize it in the viewer (PR 17964 follow-up)

This mimics the `signal` option that's available for `addEventListener`, see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal).
This commit is contained in:
Jonas Jenwald 2024-04-18 16:53:56 +02:00
parent 5ad42c13ad
commit 702ee7b1e1
3 changed files with 148 additions and 130 deletions

View file

@ -44,29 +44,21 @@ async function waitOnEventOrTimeout({ target, name, delay = 0 }) {
throw new Error("waitOnEventOrTimeout - invalid parameters.");
}
const { promise, resolve } = Promise.withResolvers();
const ac = new AbortController();
function handler(type) {
if (target instanceof EventBus) {
target._off(name, eventHandler);
} else {
target.removeEventListener(name, eventHandler);
}
ac.abort(); // Remove event listener.
clearTimeout(timeout);
if (timeout) {
clearTimeout(timeout);
}
resolve(type);
}
const eventHandler = handler.bind(null, WaitOnType.EVENT);
if (target instanceof EventBus) {
target._on(name, eventHandler);
} else {
target.addEventListener(name, eventHandler);
}
const evtMethod = target instanceof EventBus ? "_on" : "addEventListener";
target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), {
signal: ac.signal,
});
const timeoutHandler = handler.bind(null, WaitOnType.TIMEOUT);
const timeout = setTimeout(timeoutHandler, delay);
const timeout = setTimeout(handler.bind(null, WaitOnType.TIMEOUT), delay);
return promise;
}
@ -87,6 +79,7 @@ class EventBus {
this._on(eventName, listener, {
external: true,
once: options?.once,
signal: options?.signal,
});
}
@ -96,10 +89,7 @@ class EventBus {
* @param {Object} [options]
*/
off(eventName, listener, options = null) {
this._off(eventName, listener, {
external: true,
once: options?.once,
});
this._off(eventName, listener);
}
/**
@ -138,11 +128,25 @@ class EventBus {
* @ignore
*/
_on(eventName, listener, options = null) {
let rmAbort = null;
if (options?.signal instanceof AbortSignal) {
const { signal } = options;
if (signal.aborted) {
console.error("Cannot use an `aborted` signal.");
return;
}
const onAbort = () => this._off(eventName, listener);
rmAbort = () => signal.removeEventListener("abort", onAbort);
signal.addEventListener("abort", onAbort);
}
const eventListeners = (this.#listeners[eventName] ||= []);
eventListeners.push({
listener,
external: options?.external === true,
once: options?.once === true,
rmAbort,
});
}
@ -155,7 +159,9 @@ class EventBus {
return;
}
for (let i = 0, ii = eventListeners.length; i < ii; i++) {
if (eventListeners[i].listener === listener) {
const evt = eventListeners[i];
if (evt.listener === listener) {
evt.rmAbort?.(); // Ensure that the `AbortSignal` listener is removed.
eventListeners.splice(i, 1);
return;
}