Skip to content

Commit

Permalink
feat(fromEvent): Aggregate multiple arguments
Browse files Browse the repository at this point in the history
If provided with an EventEmitter, `fromEvent` streams will emit an array
of values when the EventEmitter provides listeners with multiple
arguments.

The previous implementation naively provided only the first argument,
which made `fromEvent` incompatible with event interfaces that rely on
providing multiple arguments to their listeners, such as Node's
`http.Server`.

An alternative solution involved providing selector support. This wasn't
implemented for several reasons.

  1. Modifying the function signature for the extra would be a
     breaking change.
  2. An expanded function signature would become 'crowded', and one of
     the architectural goals of xstream is to keep interfaces as small
     as they need to be to achieve their goals.
  3. Aggregating emitted events into an array is consistent with the
     implementation in Most.js, and achieves similar flexibility with Rx
     when providing a selector function to `.map()` on a result stream.

`fromEvent` will now emit mixed-types. If consumers are not responsible
for calling `.emit()` on the source emitter, they should implement
appropriate guards to ensure they are dealing with an intended type.

Should resolve staltz#84.
  • Loading branch information
Christian Johns committed Jul 22, 2016
1 parent ebd4a28 commit 92c2aad
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 1 deletion.
28 changes: 27 additions & 1 deletion src/extra/fromEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export class NodeEventProducer implements InternalProducer<any> {
constructor(private node: EventEmitter, private eventName: string) { }

_start(out: InternalListener<any>) {
this.listener = (e: any) => out._n(e);
this.listener = (...args: Array<any>) => {
return (args.length > 1) ? out._n([...args]) : out._n(args[0]);
};
this.node.addListener(this.eventName, this.listener);
}

Expand All @@ -48,6 +50,10 @@ function isEmitter(element: any): boolean {
* - DOM events with the name `eventName` from a provided target node
* - Events with the name `eventName` from a provided NodeJS EventEmitter
*
* When creating a steam from EventEmitters, if the source event has more than
* one argument all the arguments will be aggregated into an array in the
* result stream.
*
* Marble diagram:
*
* ```text
Expand Down Expand Up @@ -96,6 +102,26 @@ function isEmitter(element: any): boolean {
* > 'bar'
* ```
*
* ```js
* import fromEvent from 'xstream/extra/fromEvent'
* import {EventEmitter} from 'events'
*
* const MyEmitter = new EventEmitter()
* const stream = fromEvent(MyEmitter, 'foo')
*
* stream.addListener({
* next: i => console.log(i),
* error: err => console.error(err),
* complete: () => console.log('completed')
* })
*
* MyEmitter.emit('foo', 'bar', 'baz', 'buzz')
* ```
*
* ```text
* > ['bar', 'baz', 'buzz']
* ```
*
* @param {EventTarget|EventEmitter} element The element upon which to listen.
* @param {string} eventName The name of the event for which to listen.
* @param {boolean?} useCapture An optional boolean that indicates that events of
Expand Down
21 changes: 21 additions & 0 deletions tests/extra/fromEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,25 @@ describe('fromEvent (extra) - EventEmitter', () => {
target.emit( 'test', 1 );
target.emit( 'test', 2 );
});

it('should aggregate arguments from emitters', (done) => {
const target = new FakeEventEmitter();
const stream = fromEvent(target, 'test').take(2);

let expected = [[1, 'foo', true], [2, 'bar', false]];

stream.addListener({
next: (x: any) => {
assert.deepEqual(x, expected.shift());
},
error: (err: any) => done(err),
complete: () => {
assert.strictEqual(expected.length, 0);
done();
}
});

target.emit( 'test', 1, 'foo', true );
target.emit( 'test', 2, 'bar', false );
});
});

0 comments on commit 92c2aad

Please sign in to comment.