Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Various EventEmitter event replacements with synchronous, a-synchronous, and queued events. Made in TypeScript so usable with JavaScript and TypeScript.
A library for sending spontaneous events similar to Qt signal/slot or C# events. It replaces EventEmitter, and instead makes each event into a member which is its own little emitter. Implemented in TypeScript (typings file included) and usable with JavaScript as well.
You may also want to check out https://github.com/garronej/ts-evt which is a library with a similar approach.
Synchronous events:
import {SyncEvent} from 'ts-events';
const evtChange = new SyncEvent<string>();
evtChange.attach(function(s) {
console.log(s);
});
evtChange.post('hi!');
// at this point, 'hi!' was already printed on the console
A-synchronous events:
import {AsyncEvent} from 'ts-events';
const evtChange = new AsyncEvent<string>();
evtChange.attach(function(s) {
console.log(s);
});
evtChange.post('hi!');
// 'hi!' will be printed to the console in the next Node.JS cycle
Queued events for fine-grained control:
import {QueuedEvent} from 'ts-events';
import * as tsEvents from 'ts-events';
const evtChange = new QueuedEvent<string>();
evtChange.attach(function(s) {
console.log(s);
});
evtChange.post('hi!');
// the event is still in a global queue
tsEvents.flush();
// now, 'hi!' has been written to the console
Different ways of attaching:
// attach a function
evtChange.attach(function(s) {
console.log(s);
});
// attach a function bound to an object
evtChange.attach(this, this.onChange);
// directly attach another event
evtChange.attach(this.evtChange);
// like EventEmitter.once(), add a handler which is automatically detached after being called
evtChange.once(function(s) {
console.log(s);
});
Versatile events, let the subscriber choose:
import {AnyEvent} from 'ts-events';
import * as tsEvents from 'ts-events';
const evtChange = new AnyEvent<string>();
evtChange.attachSync(function(s) {
console.log(s + ' this is synchronous.');
});
evtChange.attachAsync(function(s) {
console.log(s + ' this is a-synchronous.');
});
evtChange.attachQueued(function(s) {
console.log(s + ' this is queued.');
});
evtChange.post('hi!');
tsEvents.flush(); // only needed for queued
evtChange.onceAsync(function(s) {
console.log(s + ' after this event, I will be detached and print no more');
});
// similar functions onceSync() and onceQueued() exist.
Install using: npm install ts-events
or yarn install ts-events
.
Then require the module in your code:
// JavaScript
var tc = require("ts-events");
// TypeScript
import * as tsEvents from "ts-events";
There are two options:
ts-events supports three event types: Synchronous, A-synchronous and Queued. Here is a comparison:
Event Type | Handler Invocation | Condensable? |
---|---|---|
Synchronous | directly, within the call to post() | no |
A-synchronous | in the next Node.JS cycle | yes |
Queued | when you flush the queue manually | yes |
In the table above, 'condensable' means that you can choose to condense multiple sent events into one: e.g. for an a-synchronous event, you can opt that if it is sent more than once in a Node.JS cycle, the event handlers are invoked only once.
There is a fourth event called AnyEvent, which can act as a Sync/Async/Queued event depending on how you attach listeners.
If you want EventEmitter-style events, then use SyncEvent. The handlers of SyncEvents are called directly when you emit the event.
import {SyncEvent} from 'ts-events';
const myEvent = new SyncEvent();
myEvent.attach(function(s) {
console.log(s);
});
myEvent.post('hi!');
// at this point, 'hi!' was already printed on the console
Typically you use events as members in a class, instead of extending EventEmitter:
import {SyncEvent} from 'ts-events';
export class Counter {
/**
* This event is called whenever the counter changes
* @param n The counter value
*/
public evtChanged: SyncEvent<number> = new SyncEvent<number>();
/**
* The counter value
*/
private _n = 0;
public inc(): void {
this._n++;
this.evtChanged.post(this._n);
}
};
const ctr = new Counter();
// Attach a handler to the event
// Do this instead of ctr.on('changed', ...)
ctr.evtChanged.attach((n: number): void => {
console.log('The counter changed to: ' + n.toString(10));
});
ctr.inc();
// Here, the event handler is already called and you see a log line on the console
As you can see, each event is its own little emitter.
Suppose that the handler for an event - directly or indirectly - causes the same event to be sent. For synchronous events, this would mean an infinite loop. SyncEvents have protection built-in: if a handler causes the same event to get posted 10 times recursively, an error is thrown. You can change or disable this behaviour with the static variable SyncEvent.MAX_RECURSION_DEPTH. Set it to undefined or null to disable or to a number greater than 0 to trigger the error sooner or later.
Synchronous events (like Node.JS EventEmitter events) have the nasty habit of invoking handlers when they don't expect it. Therefore we also have a-synchronous events: when you post an a-synchronous event, the handlers are called in the next Node.JS cycle. To use, simply use AsyncEvent instead of SyncEvent in the example above.
By default, AsyncEvent uses setImmediate() to defer a call to the next Node.JS cycle. You can change that by calling the static function AsyncEvent.setScheduler().
import {AsyncEvent} from 'ts-events';
// Replace the default setImmediate() call by a setTimeout(, 0) call
AsyncEvent.setScheduler(function(callback) {
setTimeout(callback, 0);
})
For fine-grained control, use a QueuedEvent instead of an AsyncEvent. All queued events remain in one queue until you flush it.
import {QueuedEvent} from 'ts-events';
import * as tsEvents from 'ts-events';
export class Counter {
/**
* This event is called whenever the counter changes
* @param n The counter value
*/
public evtChanged: QueuedEvent<number> = new QueuedEvent<number>();
/**
* The counter value
*/
private _n = 0;
public inc(): void {
this._n++;
this.evtChanged.post(this._n);
}
};
const ctr = new Counter();
// Attach a handler to the event
// Do this instead of ctr.on('changed', ...)
ctr.evtChanged.attach((n: number): void => {
console.log('The counter changed to: ' + n.toString(10));
});
ctr.inc();
// Here, the event handler is not called yet
// Flush the event queue
tsEvents.flush();
// Here, the handler is called
You can put different events in different queues. By default, all events go into one global queue. To assign a specific queue to an event, do this:
import {QueuedEvent, EventQueue} from 'ts-events';
const myQueue = new EventQueue();
const myEvent = new QueuedEvent({ queue: myQueue });
myEvent.post('hi!');
// flush only my own queue
myQueue.flush();
Event queues have two flush functions:
The flush() function has a safeguard: by default, if it needs more than 10 iterations to clear the queue, it throws an error saying there is an endless recursion going on. You can give it a different limit if you like. Simply call e.g. flush(100) to set the limit to 100.
Event queues have two synchronous events themselves that fire when the queue becomes empty (evtDrained) or non-empty (evtFilled). The Filled event only occurs when an event is added to an empty queue OUTSIDE of a flush operation. The Drained event occurs at the end of a flush operation if the queue is flushed empty. To check whether the queue is empty, use the empty() method.
For a-synchronous events and for queued events, you can opt to condense multiple post() calls into one. If multiple post() calls happen before the handlers are called, the handlers are invoked only once, with the argument from the last post() call.
import {AsyncEvent} from 'ts-events';
// create a condensed event
const myEvent = new AsyncEvent<string>({ condensed: true });
myEvent.attach(function(s) {
console.log(s);
});
myEvent.post('hi!');
myEvent.post('bye!');
// after a cycle, only 'bye!' is logged to the console
There is no need to use .bind(this) when attaching a handler. Simply call myEvent.attach(this, myFunc);
There are clear semantics for the effect of attach() and detach(). These semantics were chosen to prevent surprises, however there is no reason why we should not support different semantics in the future. Please submit an issue if you need a different implementation.
Attaching has the following forms:
const obj = {};
const handler = function() {
};
const myEvent = new AsyncEvent<string>();
const myOtherEvent = new AsyncEvent<string>();
myEvent.attach(handler); // will call handler with this === myEvent
myEvent.attach(obj, handler); // will call handler with this === obj
myEvent.attach(myOtherEvent); // will post myOtherEvent
Detaching has the following forms:
const obj = {};
const handler = function() {
};
const myEvent = new AsyncEvent<string>();
const myOtherEvent = new AsyncEvent<string>();
myEvent.detach(handler); // detaches all instances of the given handler
myEvent.detach(obj); // detaches all handlers bound to the given object
myEvent.detach(obj, handler); // detaches only the given handler bound to the given object
myEvent.detach(myOtherEvent); // detaches only myOtherEvent
myEvent.detach(); // detaches all handlers
// returned detacher function
const detacher = myEvent.attach(handler);
detacher(); // detachers `handler`
Note that when you attach an AsyncEvent to another AsyncEvent, the handlers of both events are called in the very next cycle, i.e. it does not take 2 cycles to call all handlers. This is 'decoupled enough' for most purposes and reduces latency.
The old EventEmitter treats 'error' events different from events with other names. If you emit them at a time when there are no listeners attached, then an error is thrown. You can get the same behaviour by using an ErrorSyncEvent, ErrorAsyncEvent or ErrorQueuedEvent.
const myEvent = new ErrorSyncEvent();
// this throws: 'error event posted while no listeners attached. Error: foo'
myEvent.post(new Error('foo'));
myEvent.attach((e: Error): void => {});
// this simply calls the event handler with the given error
myEvent.post(new Error('foo'));
The AnyEvent class lets you choose between sync/async/queued in the attach() function. For instance:
import {AnyEvent, EventType} from 'ts-events';
import * as tsEvents from 'ts-events';
const evtChange = new AnyEvent();
evtChange.attach(function(s) {
console.log(s + ' this is synchronous.');
});
evtChange.attach(EventType.Sync, function(s) {
console.log(s + ' this is synchronous.');
});
evtChange.attach(EventType.Async, function(s) {
console.log(s + ' this is a-synchronous.');
});
evtChange.attach(EventType.Queued, function(s) {
console.log(s + ' this is queued.');
});
evtChange.once(function(s) {
console.log(s + ' this is synchronous and will be called only once.');
});
evtChange.once(EventType.Sync, function(s) {
console.log(s + ' this is synchronous and will be called only once.');
});
evtChange.once(EventType.Async, function(s) {
console.log(s + ' this is a-synchronous and will be called only once.');
});
evtChange.once(EventType.Queued, function(s) {
console.log(s + ' this is queued and will be called only once.');
});
// convenience functions:
evtChange.attachSync(function(s) {
console.log(s + ' this is conveniently synchronous.');
});
evtChange.attachAsync(function(s) {
console.log(s + ' this is conveniently a-synchronous and condensed.');
}, { condensed: true });
evtChange.attachAsync(function(s) {
console.log(s + ' this is conveniently a-synchronous and not condensed.');
});
evtChange.attachQueued(function(s) {
console.log(s + ' this is conveniently queued.');
});
// convenience functions:
evtChange.onceSync(function(s) {
console.log(s + ' this is conveniently synchronous and will be called only once.');
});
evtChange.onceAsync(function(s) {
console.log(s + ' this is conveniently a-synchronous and condensed and will be called only once.');
}, { condensed: true });
evtChange.onceAsync(function(s) {
console.log(s + ' this is conveniently a-synchronous and not condensed and will be called only once.');
});
evtChange.onceQueued(function(s) {
console.log(s + ' this is conveniently queued and will be called only once.');
});
evtChange.post('hi!');
tsEvents.flush(); // only needed for queued
A TypeScript annoyance: when you create an event with a void argument, TypeScript forces you to pass 'undefined' to post(). To overcome this, we added VoidSyncEvent, VoidAsyncEvent and VoidQueuedEvent classes.
const myEvent = new SyncEvent<void>();
// annoying: have to pass undefined to post() to make it compile
myEvent.post(undefined)
// Solution:
const myEvent = new VoidSyncEvent();
myEvent.post(); // no need to pass 'undefined'
Each type of event has a member evtListenersChanged: VoidSyncEvent
to notify you when someone attaches or detaches event handlers.
v3.4.1 (2022-02-04)
v3.4.0 (2020-02-07)
v3.3.1 (2019-06-04)
v3.3.0 (2019-06-02)
attach()
and once()
methods.v3.2.1 (2019-02-01)
v3.2.0 (2017-03-31)
v3.1.5 (2017-01-11)
v3.1.4 (2016-11-26)
v3.1.3 (2016-10-28)
v3.1.2 (2016-10-27)
v3.1.1 (2016-02-27)
v3.1.0 (2016-02-27)
v3.0.1
v3.0.0
v2.4.0
v2.3.0 (2016-02-20)
v2.2.0 (2016-02-20)
v2.1.1 (2016-02-20)
v2.1.0 (2016-02-20)
v2.0.0
v1.1.0 (2015-05-25):
v1.0.0 (2015-04-30):
v0.0.6 (2015-04-29):
v0.0.5 (2015-04-29):
v0.0.4 (2015-04-29):
v0.0.3 (2015-04-28):
v0.0.2 (2015-04-27):
v0.0.1 (2015-04-27):
Copyright � 2015 Rogier Schouten github@workingcode.ninja ISC (see LICENSE file in repository root).
FAQs
Various EventEmitter event replacements with synchronous, a-synchronous, and queued events. Made in TypeScript so usable with JavaScript and TypeScript.
The npm package ts-events receives a total of 848 weekly downloads. As such, ts-events popularity was classified as not popular.
We found that ts-events demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.