evnty
Version:
0-Deps, simple, fast, for browser and node js reactive anonymous event library
359 lines (357 loc) • 10.7 kB
JavaScript
import { Callable } from "./callable.js";
import { Sequence } from "./sequence.js";
export * from "./types.js";
export * from "./callable.js";
export * from "./signal.js";
export * from "./sequence.js";
export const removeListener = (listeners, listener)=>{
let index = listeners.indexOf(listener);
const wasRemoved = index !== -1;
while(~index){
listeners.splice(index, 1);
index = listeners.indexOf(listener);
}
return wasRemoved;
};
export const setTimeoutAsync = (timeout, signal)=>new Promise((resolve)=>{
const timerId = setTimeout(resolve, timeout, true);
signal?.addEventListener('abort', ()=>{
clearTimeout(timerId);
resolve(false);
});
});
export class Unsubscribe extends Callable {
_done = false;
constructor(callback){
super(async ()=>{
this._done = true;
await callback();
});
}
get done() {
return this._done;
}
pre(callback) {
return new Unsubscribe(async ()=>{
await callback();
await this();
});
}
post(callback) {
return new Unsubscribe(async ()=>{
await this();
await callback();
});
}
countdown(count) {
return new Unsubscribe(async ()=>{
if (!--count) {
await this();
}
});
}
}
var HookType = /*#__PURE__*/ function(HookType) {
HookType[HookType["Add"] = 0] = "Add";
HookType[HookType["Remove"] = 1] = "Remove";
return HookType;
}(HookType || {});
export class Event extends Callable {
listeners;
hooks = [];
_disposed = false;
dispose;
constructor(dispose){
const listeners = [];
super((value)=>Promise.all(listeners.map(async (listener)=>listener(await value))));
this.listeners = listeners;
this.dispose = ()=>{
this._disposed = true;
void this.clear();
void this._error?.dispose();
void dispose?.();
};
}
_error;
get error() {
return this._error ??= new Event();
}
get size() {
return this.listeners.length;
}
get disposed() {
return this._disposed;
}
lacks(listener) {
return this.listeners.indexOf(listener) === -1;
}
has(listener) {
return this.listeners.indexOf(listener) !== -1;
}
off(listener) {
if (removeListener(this.listeners, listener) && this.hooks.length) {
[
...this.hooks
].forEach((spy)=>spy(listener, 1));
}
return this;
}
on(listener) {
this.listeners.push(listener);
if (this.hooks.length) {
[
...this.hooks
].forEach((spy)=>spy(listener, 0));
}
return new Unsubscribe(()=>{
void this.off(listener);
});
}
once(listener) {
const oneTimeListener = (event)=>{
void this.off(oneTimeListener);
return listener(event);
};
return this.on(oneTimeListener);
}
clear() {
this.listeners.splice(0);
if (this.hooks.length) {
[
...this.hooks
].forEach((spy)=>spy(undefined, 1));
}
return this;
}
then(onfulfilled, onrejected) {
const subscribers = [];
const promise = new Promise((resolve, reject)=>{
subscribers.push(this.once(resolve));
subscribers.push(this.error.once(reject));
});
const unsubscribe = async (value)=>{
await Promise.all(subscribers.map((u)=>u()));
return value;
};
return promise.then(onfulfilled, onrejected).then(unsubscribe).catch(async (error)=>{
throw await unsubscribe(error);
});
}
async settle() {
return await Promise.allSettled([
this.promise
]).then(([settled])=>settled);
}
get promise() {
return this.then();
}
[Symbol.asyncIterator]() {
const ctrl = new AbortController();
const sequence = new Sequence(ctrl.signal);
const emitEvent = (value)=>{
sequence(value);
};
const unsubscribe = this.on(emitEvent).pre(()=>{
ctrl.abort('done');
});
const spy = (target = emitEvent, action)=>{
if (target === emitEvent && action === 1) {
void unsubscribe();
}
};
this.hooks.push(spy);
return sequence[Symbol.asyncIterator]();
}
pipe(generator) {
const emitEvent = async (value)=>{
try {
for await (const generatedValue of generator(value)){
await result(generatedValue).catch(result.error);
}
} catch (e) {
await result.error(e);
}
};
const unsubscribe = this.on(emitEvent).pre(()=>{
removeListener(this.hooks, hook);
});
const hook = (target = emitEvent, action)=>{
if (target === emitEvent && action === 1) {
void unsubscribe();
}
};
this.hooks.push(hook);
const result = new Event(unsubscribe);
return result;
}
async *generator(generator) {
for await (const value of this.pipe(generator)){
yield value;
}
}
filter(filter) {
return this.pipe(async function*(value) {
if (await filter(value)) {
yield value;
}
});
}
first(filter) {
const filteredEvent = this.pipe(async function*(value) {
if (await filter(value)) {
yield value;
await filteredEvent.dispose();
}
});
return filteredEvent;
}
map(mapper) {
return this.pipe(async function*(value) {
yield await mapper(value);
});
}
reduce(reducer, ...init) {
let hasInit = init.length === 1;
let result = init[0];
return this.pipe(async function*(value) {
if (hasInit) {
result = await reducer(result, value);
yield result;
} else {
result = value;
hasInit = true;
}
});
}
expand(expander) {
return this.pipe(async function*(value) {
const values = await expander(value);
for (const value of values){
yield value;
}
});
}
orchestrate(conductor) {
let initialized = false;
let lastValue;
const unsubscribe = this.on((event)=>{
initialized = true;
lastValue = event;
});
const unsubscribeConductor = conductor.on(async ()=>{
if (initialized) {
await orchestratedEvent(lastValue);
initialized = false;
}
});
const orchestratedEvent = new Event(unsubscribe.post(unsubscribeConductor));
return orchestratedEvent;
}
debounce(interval) {
let controller = new AbortController();
return this.pipe(async function*(value) {
controller.abort();
controller = new AbortController();
const complete = await setTimeoutAsync(interval, controller.signal);
if (complete) {
yield value;
}
});
}
throttle(interval) {
let timeout = 0;
let pendingValue;
let hasPendingValue = false;
return this.pipe(async function*(value) {
const now = Date.now();
if (timeout <= now) {
timeout = now + interval;
yield value;
} else {
pendingValue = value;
if (!hasPendingValue) {
hasPendingValue = true;
await setTimeoutAsync(timeout - now);
timeout = now + interval;
hasPendingValue = false;
yield pendingValue;
}
}
});
}
batch(interval, size) {
let controller = new AbortController();
const batch = [];
return this.pipe(async function*(value) {
batch.push(value);
if (size !== undefined && batch.length >= size) {
controller.abort();
yield batch.splice(0);
}
if (batch.length === 1) {
controller = new AbortController();
const complete = await setTimeoutAsync(interval, controller.signal);
if (complete) {
yield batch.splice(0);
}
}
});
}
queue() {
const ctrl = new AbortController();
const sequence = new Sequence(ctrl.signal);
const onEvent = (value)=>{
sequence(value);
};
const unsubscribe = this.on(onEvent).pre(()=>{
ctrl.abort('done');
});
const pop = async ()=>await sequence;
return {
pop,
stop: async ()=>{
await unsubscribe();
},
get stopped () {
return ctrl.signal.aborted;
},
then (onfulfilled, onrejected) {
return pop().then(onfulfilled, onrejected);
},
[Symbol.asyncIterator] () {
return {
next: async ()=>{
try {
const value = await pop();
return {
value,
done: false
};
} catch {
return {
value: undefined,
done: true
};
}
}
};
}
};
}
}
export const merge = (...events)=>{
const mergedEvent = new Event();
events.forEach((event)=>event.on(mergedEvent));
return mergedEvent;
};
export const createInterval = (interval)=>{
let counter = 0;
const intervalEvent = new Event(()=>clearInterval(timerId));
const timerId = setInterval(()=>{
void intervalEvent(counter++);
}, interval);
return intervalEvent;
};
export const createEvent = ()=>new Event();
export default createEvent;
//# sourceMappingURL=index.js.map