Closed mustafa519 closed 12 months ago
Hi! Thank you
tseep
is highly optimized around emit so I can't change emit
method to support wildcard calls.
I think I'll add wildcard later in core if there will be more issues about it.
Right now you could implement it like this in your project:
import { EventEmitter as Tseep, DefaultEventMap } from 'tseep';
export class EventEmitter<EventMap extends DefaultEventMap> extends Tseep<EventMap> {
emitw(wildcard: string, ...args: any[]) {
for (const ename of this.eventNames()) {
// if ename extends wildcard
this.emit(ename, ...args);
}
}
}
And check args with types like this:
type FilterKeys<ww extends string, EventKeys extends string> = ww extends `${infer E extends string}:*:${infer Rest extends string}` ?
FilterKeys<Rest, Extract<EventKeys, `${E}:${string}`>> :
ww extends `${infer E extends string}:*` ?
Extract<EventKeys, `${E}:${string}`>
: never;
// inside class
emitw<
Wildcard extends string,
Keys = FilterKeys<Wildcard, Extract<keyof EventMap, string>>
>(w: Wildcard, ...args: Keys extends keyof EventMap ? Parameters<EventMap[Keys]> : never) {
for (const ename of this.eventNames()) {
// if ename extends wildcard
this.emit(ename, ...args);
}
}
After all if you want to optimize it, you should have some special graph structure around event name list, so you could rid of loop and check on every call.
This graph should be updated with custom addListener / removeListener that overloads default methods.
For example DAWG graph structure. (Found js implementation as PTrie from https://github.com/mckoss/dawg).
Should be benchmarked anyway, because current DAWG implementation may be slower than just for loop & regex check every time.
As another question, is it also possible to order the execution of events, like async support or something like that?
If I understand you correctly, you want to wait for async listener to complete and only after that call next listener when emitting. First of all it will significantly impact performance, so may be will be faster use classic event emitter.
I did not test it & did not benchmark it yet, but TaskCollection
in tseep supports it by passing true to last constructor argument of TaskCollection
.
You also should fork this package and update addListener
and prependListener
TaskCollection
is the thing that unrolls call loop so it calls array of functions as single function without loop.
Code generation for loop unroll is here
Oh, you literally implemented the logic in the issue already. I do appreciate that you shared detailed above, and so its types also. I'll try to add that logic by my own additions, thanks!
I think, I am not good enough to implement something like DAWG. Still it helped what you mean, even if I'd go for something simple like you mentioned regex etc.
IMHO, multi-level wildcard is kinda necessary in an emitter. Like:
// For instance we have three type of db that we watch
// db:mysql:error
// db:postgres:error
// db:redis:error
emitter.on('db:*:error', (e) =>
{
writeErrorLog(e, 'db');
fatal(e);
});
// or
emitter.on('db:*:init', (dbName) =>
{
hasInitialized.dbs[dbName] = true;
});
// or a websocket event impl.
// ws:room:user --- 1...n (room id)
// ws:typing --- 1..n (user id)
// sse:notification:profile --- 1..n (user id)
// sse:notification:mail --- 1..n (user id)
emitter.on('sse:notification:*', ([firstWildcardParam], ...args) =>
{
if (firstWildcardParam === 'mail')
{
const { sender, to, body, attachments ) = args[0];
addQueue('smtp', { sender, to, body, attachments });
}
});
I'm particularly excited about the prospect of leveraging this high-performance library for SSE and ws events.
I'll patiently await your implementation of wildcard support, or I may explore implementing it myself by forking the repository when the need arises by referencing your advice that you shared above in this issue.
Thank you for your prompt and detailed response.
I think in this cases you could just pass some payload as arguments to distinguish this wildcard levels.
For example with DBs:
type DbInitEvent = {
type: 'mysql',
// mysql payload
} | {
type: 'pg',
// pg payload
};
emitter.on('db:init', e => {
if (e.type === 'mysql') {
// handle mysql
}
// etc
});
I mean you could move this abstractions somewhere else. It will be lighter, much more readable and maintainable.
Then also all logic around wildcards will disappear and there will be no need to optimize it 😄.
Yeah, in this library if I inherit the event listener I can check in another one which is going to be optimized by default.
I haven't thought this. You led me to question on myself why I made the things more complex than it requires, lol.
I hope I'll present your art to the community when I release what I am building.
Thank you for the support.
Waiting for results! Wish you a good luck 🙌🏻
Hello! I like your efforts, and I am very interested in this package. I am developing a framework like structure and was looking for a performant event emitter which works on browser, node.js to use it as a part of the event manager. Do you consider supporting namespace wildcards?
Example implementation:
As another question, is it also possible to order the execution of events, like async support or something like that?
Thanks!