@virtualstate/app-history
Version:
Native JavaScript [app-history](https://github.com/WICG/app-history) implementation
81 lines • 3.82 kB
JavaScript
import { isParallelEvent } from "./parallel-event.js";
import { isSignalEvent, isSignalHandled } from "./signal-event.js";
import { AbortError } from "../app-history-errors.js";
import { EventTargetListenersMatch, EventTargetListenersThis } from "./event-target-options.js";
import { EventTargetListeners } from "./event-target-listeners.js";
export class AsyncEventTarget extends EventTargetListeners {
[EventTargetListenersThis];
constructor(thisValue = undefined) {
super();
this[EventTargetListenersThis] = thisValue;
}
async dispatchEvent(event) {
const listeners = this[EventTargetListenersMatch]?.(event.type) ?? [];
// Don't even dispatch an aborted event
if (isSignalEvent(event) && event.signal.aborted) {
throw new AbortError();
}
const parallel = isParallelEvent(event);
const promises = [];
for (let index = 0; index < listeners.length; index += 1) {
const descriptor = listeners[index];
const promise = (async () => {
// Remove the listener before invoking the callback
// This ensures that inside of the callback causes no more additional event triggers to this
// listener
if (descriptor.once) {
// by passing the descriptor as the options, we get an internal redirect
// that forces an instance level object equals, meaning
// we will only remove _this_ descriptor!
this.removeEventListener(descriptor.type, descriptor.callback, descriptor);
}
await descriptor.callback.call(this[EventTargetListenersThis] ?? this, event);
})();
if (!parallel) {
try {
await promise;
}
catch (error) {
if (!isSignalHandled(event, error)) {
await Promise.reject(error);
}
}
if (isSignalEvent(event) && event.signal.aborted) {
// bye
return;
}
}
else {
promises.push(promise);
}
}
if (promises.length) {
// Allows for all promises to settle finish so we can stay within the event, we then
// will utilise Promise.all which will reject with the first rejected promise
const results = await Promise.allSettled(promises);
const rejected = results.filter((result) => {
return result.status === "rejected";
});
if (rejected.length) {
let unhandled = rejected;
// If the event was aborted, then allow abort errors to occur, and handle these as handled errors
// The dispatcher does not care about this because they requested it
//
// There may be other unhandled errors that are more pressing to the task they are doing.
//
// The dispatcher can throw an abort error if they need to throw it up the chain
if (isSignalEvent(event) && event.signal.aborted) {
unhandled = unhandled.filter(result => !isSignalHandled(event, result.reason));
}
if (unhandled.length === 1) {
await Promise.reject(unhandled[0].reason);
throw unhandled[0].reason; // We shouldn't get here
}
else if (unhandled.length > 1) {
throw new AggregateError(unhandled.map(({ reason }) => reason));
}
}
}
}
}
//# sourceMappingURL=async-event-target.js.map