Skip to content

Commit

Permalink
feat(): add default listener functionality
Browse files Browse the repository at this point in the history
Added new static and instance setDefaultListener methods
to allow signal creators to set default listeners to be used
when no other listener is added. This should typically be used
to report errors or log when no listener is added as it can
be considered a programmer error in some use cases.
  • Loading branch information
Justin Maher committed Mar 4, 2019
1 parent b789aff commit e6085d0
Show file tree
Hide file tree
Showing 5 changed files with 401 additions and 3 deletions.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,52 @@ without storing any state itself. This is how the internal signal transformation
remove unnecessary intermediate signals. Feel free to use or ignore the ExtendedSignal at your
discretion.

### Default Listeners

Default listeners allow the signal owner to define some default
action that should be taken when a signal is dispatched but no
listeners have been added. Typically this would be used to throw
or log an error if no listeners are present for an important signal.

Default listeners can be set with either the static
setDetaultListener method or the instance setDefaultListener method.

The static default listener will be called on any signal that has
no listeners present (even if no instance default listener is set).

The instance default listener will only be called when the
specific signal instance is dispatched with no listeners present.



```ts
import {Signal} from 'micro-signals';
import * as assert from 'assert';

// static default listener
const staticPayloads: string[] = [];
const signal = new Signal<string>();

Signal.setDefaultListener(payload => staticPayloads.push(payload));

signal.dispatch('hello static listener');

assert.equal(staticPayloads.length, 1);
assert.equal(staticPayloads[0], 'hello static listener');

// instance default listener
const instancePayloads: string[] = [];
signal.setDefaultListener(payload => instancePayloads.push(payload));

signal.dispatch('hello instance listener');

// static default listener is not used once instance default listener has been set.
assert.equal(staticPayloads.length, 1);
assert.equal(instancePayloads.length, 1);
assert.equal(instancePayloads[0], 'hello instance listener');
```


### Interfaces

Several interfaces are exported as well for convenience:
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export interface ReadableSignal<T> extends BaseSignal<T> {

export interface WritableSignal<T> {
dispatch: (payload: T) => void;
/**
* set a listener to be called if no other listeners are available.
*/
setDefaultListener(listener: Listener<T>): void;
}

export interface Cache<T> {
Expand Down
28 changes: 25 additions & 3 deletions src/signal.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import {Listener, WritableSignal} from './interfaces';

import {ExtendedSignal} from './extended-signal';
import { ReadableSignal } from './index';
import {ReadableSignal} from './index';
import {Listener, WritableSignal} from './interfaces';

export class Signal<T> extends ExtendedSignal<T> implements WritableSignal<T>, ReadableSignal<T> {
public static setDefaultListener(listener: Listener<any>) {
Signal._staticDefaultListener = listener;
}

private static _staticDefaultListener: Listener<any> = () => {
// default static listener is a noop
}

protected _listeners = new Set<Listener<T>>();

constructor() {
super({
add: listener => {
Expand All @@ -15,7 +23,21 @@ export class Signal<T> extends ExtendedSignal<T> implements WritableSignal<T>, R
},
});
}

public dispatch(payload: T): void {
if (this._listeners.size === 0) {
const instanceDefaultListener = this._instanceDefaultListener;
instanceDefaultListener(payload);
return;
}

this._listeners.forEach(callback => callback.call(undefined, payload));
}

public setDefaultListener(listener: Listener<T>) {
this._instanceDefaultListener = listener;
}

private _instanceDefaultListener: Listener<T>
= payload => Signal._staticDefaultListener(payload)
}
54 changes: 54 additions & 0 deletions test/signal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,57 @@ test('addOnce should the same as add when adding a listener multiple times', t =

t.end();
});

test('dispatches to static default listener if no instance default listener is set', t => {
const staticCalls: any[][] = [];
const staticDefaultListener = (...args: any[]) => staticCalls.push(args);
Signal.setDefaultListener(staticDefaultListener);
const s = new Signal<number>();
s.dispatch(1);
t.equal(staticCalls[0][0], 1);
t.end();
});

test('dispatches to instance default listener when it is set', t => {
const staticCalls: any[][] = [];
const instanceCalls: any[][] = [];
const staticDefaultListener = (...args: any[]) => staticCalls.push(args);
const instanceDefaultListener = (...args: any[]) => instanceCalls.push(args);
Signal.setDefaultListener(staticDefaultListener);
const s = new Signal<number>();
s.setDefaultListener(instanceDefaultListener);
s.dispatch(1);
t.equal(staticCalls.length, 0);
t.equal(instanceCalls[0][0], 1);
t.end();
});

test('does not dispatch to static default listener when other listeners have been added', t => {
const staticCalls: number[] = [];
const staticDefaultListener = (payload: number) => staticCalls.push(payload);
Signal.setDefaultListener(staticDefaultListener);
const s = new Signal<number>();
s.add(() => {
// no-op
});

s.dispatch(1);

t.equal(staticCalls.length, 0);
t.end();
});

test('does not dispatch to static default listener when other listeners have been added', t => {
const instanceCalls: number[] = [];
const instanceDefaultListener = (payload: number) => instanceCalls.push(payload);
const s = new Signal<number>();
s.setDefaultListener(instanceDefaultListener);
s.add(() => {
// no-op
});

s.dispatch(1);

t.equal(instanceCalls.length, 0);
t.end();
});
Loading

0 comments on commit e6085d0

Please sign in to comment.