From ad63372104a3888cbff4a7c3fee8733033973e96 Mon Sep 17 00:00:00 2001 From: Andre Staltz Date: Sun, 12 Jun 2016 21:35:32 +0300 Subject: [PATCH] feat(imitate): move imitate() from MimicStream to Stream BREAKING CHANGE: MimicStream and xs.createMimic() were removed entirely. The imitate() method now exists on every Stream instance. To use the proxy stream technique, use xs.create() to create the proxy, then call proxy.imitate(other). --- src/core.ts | 134 ++++++++---------- src/index.ts | 4 +- tests/{mimicStream.ts => operator/imitate.ts} | 72 ++++------ tests/stream.ts | 4 +- 4 files changed, 91 insertions(+), 123 deletions(-) rename tests/{mimicStream.ts => operator/imitate.ts} (62%) diff --git a/src/core.ts b/src/core.ts index 9cea620..4ed1a20 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1073,17 +1073,6 @@ export class Stream implements InternalListener { return new MemoryStream(> ( producer)); } - /** - * Creates a new MimicStream, which can `imitate` another Stream. Only a - * MimicStream has the `imitate()` method. - * - * @factory true - * @return {MimicStream} - */ - static createMimic(): MimicStream { - return new MimicStream(); - } - /** * Creates a Stream that does nothing when started. It never emits any event. * @@ -1651,59 +1640,13 @@ export class Stream implements InternalListener { } /** - * Forces the Stream to emit the given value to its listeners. - * - * As the name indicates, if you use this, you are most likely doing something - * The Wrong Way. Please try to understand the reactive way before using this - * method. Use it only when you know what you are doing. - * - * @param value The "next" value you want to broadcast to all listeners of - * this Stream. - */ - shamefullySendNext(value: T) { - this._n(value); - } - - /** - * Forces the Stream to emit the given error to its listeners. - * - * As the name indicates, if you use this, you are most likely doing something - * The Wrong Way. Please try to understand the reactive way before using this - * method. Use it only when you know what you are doing. - * - * @param {any} error The error you want to broadcast to all the listeners of - * this Stream. - */ - shamefullySendError(error: any) { - this._e(error); - } - - /** - * Forces the Stream to emit the "completed" event to its listeners. - * - * As the name indicates, if you use this, you are most likely doing something - * The Wrong Way. Please try to understand the reactive way before using this - * method. Use it only when you know what you are doing. - */ - shamefullySendComplete() { - this._c(); - } -} - -export class MimicStream extends Stream { - constructor() { - super(); - } - - /** - * This method exists only on a MimicStream, which is created through - * `xs.createMimic()`. *imitate* changes this current MimicStream to behave - * like the `other` given stream. + * *imitate* changes this current Stream to emit the same events that the + * `other` given Stream does. This method returns nothing. * - * The `imitate` method and the `MimicStream` type exist to allow one thing: - * **circular dependency of streams**. For instance, let's imagine that for - * some reason you need to create a circular dependency where stream `first$` - * depends on stream `second$` which in turn depends on `first$`: + * This method exists to allow one thing: **circular dependency of streams**. + * For instance, let's imagine that for some reason you need to create a + * circular dependency where stream `first$` depends on stream `second$` + * which in turn depends on `first$`: * * * ```js @@ -1714,22 +1657,22 @@ export class MimicStream extends Stream { * ``` * * However, that is invalid JavaScript, because `second$` is undefined - * on the first line. This is how a MimicStream and imitate can help solve it: + * on the first line. This is how *imitate* can help solve it: * * ```js * import delay from 'xstream/extra/delay' * - * var secondMimic$ = xs.createMimic(); - * var first$ = secondMimic$.map(x => x * 10).take(3); + * var secondProxy$ = xs.create(); + * var first$ = secondProxy$.map(x => x * 10).take(3); * var second$ = first$.map(x => x + 1).startWith(1).compose(delay(100)); - * secondMimic$.imitate(second$); + * secondProxy$.imitate(second$); * ``` * - * We create `secondMimic$` before the others, so it can be used in the + * We create `secondProxy$` before the others, so it can be used in the * declaration of `first$`. Then, after both `first$` and `second$` are - * defined, we hook `secondMimic$` with `second$` with `imitate()` to tell + * defined, we hook `secondProxy$` with `second$` with `imitate()` to tell * that they are "the same". `imitate` will not trigger the start of any - * stream, it just binds `secondMimic$` and `second$` together. + * stream, it just binds `secondProxy$` and `second$` together. * * The following is an example where `imitate()` is important in Cycle.js * applications. A parent component contains some child components. A child @@ -1737,16 +1680,12 @@ export class MimicStream extends Stream { * * * ```js - * const childActionMimic$ = xs.createMimic(); - * const parent = Parent({...sources, childAction$: childActionMimic$}); + * const childActionProxy$ = xs.create(); + * const parent = Parent({...sources, childAction$: childActionProxy$}); * const childAction$ = parent.state$.map(s => s.child.action$).flatten(); - * childActionMimic$.imitate(childAction$); + * childActionProxy$.imitate(childAction$); * ``` * - * The *imitate* method returns nothing. Instead, it changes the behavior of - * the current stream, making it re-emit whatever events are emitted by the - * given `other` stream. - * * Note, though, that **`imitate()` does not support MemoryStreams**. If we * would attempt to imitate a MemoryStream in a circular dependency, we would * either get a race condition (where the symptom would be "nothing happens") @@ -1759,7 +1698,7 @@ export class MimicStream extends Stream { * MemoryStream, you should rework your code around `imitate()` to use a * Stream instead. Look for the stream in the circular dependency that * represents an event stream, and that would be a candidate for creating a - * MimicStream which then imitates the real event stream. + * proxy Stream which then imitates the target Stream. * * @param {Stream} other The stream to imitate on the current one. Must not be * a MemoryStream. @@ -1772,6 +1711,45 @@ export class MimicStream extends Stream { } other._setHIL(this); } + + /** + * Forces the Stream to emit the given value to its listeners. + * + * As the name indicates, if you use this, you are most likely doing something + * The Wrong Way. Please try to understand the reactive way before using this + * method. Use it only when you know what you are doing. + * + * @param value The "next" value you want to broadcast to all listeners of + * this Stream. + */ + shamefullySendNext(value: T) { + this._n(value); + } + + /** + * Forces the Stream to emit the given error to its listeners. + * + * As the name indicates, if you use this, you are most likely doing something + * The Wrong Way. Please try to understand the reactive way before using this + * method. Use it only when you know what you are doing. + * + * @param {any} error The error you want to broadcast to all the listeners of + * this Stream. + */ + shamefullySendError(error: any) { + this._e(error); + } + + /** + * Forces the Stream to emit the "completed" event to its listeners. + * + * As the name indicates, if you use this, you are most likely doing something + * The Wrong Way. Please try to understand the reactive way before using this + * method. Use it only when you know what you are doing. + */ + shamefullySendComplete() { + this._c(); + } } export class MemoryStream extends Stream { diff --git a/src/index.ts b/src/index.ts index 00b20aa..c921593 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,3 @@ -import {Stream, MemoryStream, MimicStream, Listener, Producer, Operator} from './core'; -export {Stream, MemoryStream, MimicStream, Listener, Producer, Operator}; +import {Stream, MemoryStream, Listener, Producer, Operator} from './core'; +export {Stream, MemoryStream, Listener, Producer, Operator}; export default Stream; diff --git a/tests/mimicStream.ts b/tests/operator/imitate.ts similarity index 62% rename from tests/mimicStream.ts rename to tests/operator/imitate.ts index 83e6c12..302cf33 100644 --- a/tests/mimicStream.ts +++ b/tests/operator/imitate.ts @@ -1,20 +1,12 @@ -/// -/// -import xs, {Producer, Listener, Stream, MemoryStream, MimicStream} from '../src/index'; -import delay from '../src/extra/delay'; +/// +/// +import xs, {Producer, Listener, Stream, MemoryStream} from '../../src/index'; +import delay from '../../src/extra/delay'; import * as assert from 'assert'; -describe('MimicStream', () => { - it('should be creatable with xs.createMimic()', (done) => { - const stream: MimicStream = xs.createMimic(); - assert.equal(typeof stream.map, 'function'); - assert.equal(typeof stream.take, 'function'); - assert.equal(typeof stream.filter, 'function') - done(); - }); - +describe('Stream.prototype.imitate', () => { it('should be able to model a circular dependency in the stream graph', (done) => { - const fakeSecond = xs.createMimic(); + const fakeSecond = xs.create(); const first = fakeSecond.map(x => x * 10).take(3); const second = first.map(x => x + 1).startWith(1).compose(delay(1)); fakeSecond.imitate(second); @@ -33,7 +25,7 @@ describe('MimicStream', () => { }); it('should broadcast the source stream to multiple listeners', (done) => { - const fakeSecond = xs.createMimic(); + const fakeSecond = xs.create(); const first = fakeSecond.map(x => x * 10).take(3); const second = first.map(x => x + 1).startWith(1).compose(delay(100)); fakeSecond.imitate(second); @@ -80,16 +72,16 @@ describe('MimicStream', () => { }); it('should not cause leaked cyclic executions', (done) => { - const expectedMimic = [2, 4, 8, 16, 32 /* inertia due to stopping on next tick */]; + const expectedProxy = [2, 4, 8, 16, 32 /* inertia due to stopping on next tick */]; const expectedResult = [2, 4, 8, 16]; - const proxy$ = xs.createMimic(); + const proxy$ = xs.create(); const source$ = proxy$.startWith(1).map(x => x * 2) .debug(x => { try { - assert.equal(expectedMimic.length > 0, true, + assert.equal(expectedProxy.length > 0, true, 'should be expecting the next value ' + x); - assert.equal(x, expectedMimic.shift()); + assert.equal(x, expectedProxy.shift()); } catch (err) { done(err); } @@ -103,7 +95,7 @@ describe('MimicStream', () => { }, error: (err: any) => done(err), complete: () => { - assert.equal(expectedMimic.length, 1); // still waiting for 32 + assert.equal(expectedProxy.length, 1); // still waiting for 32 assert.equal(expectedResult.length, 0); setTimeout(() => { done(); @@ -112,30 +104,28 @@ describe('MimicStream', () => { }); }); - describe('imitate', () => { - it('should not by itself start the target stream execution', (done) => { - let nextDelivered = false; - const stream = xs.periodic(50).take(3).debug(() => { - nextDelivered = true; - }); - const proxyStream = xs.createMimic(); - - setTimeout(() => { - assert.equal(nextDelivered, false); - done(); - }, 125); - - proxyStream.imitate(stream); + it('should not by itself start the target stream execution', (done) => { + let nextDelivered = false; + const stream = xs.periodic(50).take(3).debug(() => { + nextDelivered = true; }); + const proxyStream = xs.create(); - it('should throw an error when given a MemoryStream', (done) => { - const stream = xs.periodic(50).take(3).remember(); - assert.strictEqual(stream instanceof MemoryStream, true); - const proxyStream = xs.createMimic(); - assert.throws(() => { - proxyStream.imitate(stream); - }); + setTimeout(() => { + assert.equal(nextDelivered, false); done(); + }, 125); + + proxyStream.imitate(stream); + }); + + it('should throw an error when given a MemoryStream', (done) => { + const stream = xs.periodic(50).take(3).remember(); + assert.strictEqual(stream instanceof MemoryStream, true); + const proxyStream = xs.create(); + assert.throws(() => { + proxyStream.imitate(stream); }); + done(); }); }); diff --git a/tests/stream.ts b/tests/stream.ts index acb63a8..1c1c430 100644 --- a/tests/stream.ts +++ b/tests/stream.ts @@ -7,7 +7,6 @@ describe('Stream', () => { it('should have all the core static operators', () => { assert.equal(typeof xs.create, 'function'); assert.equal(typeof xs.createWithMemory, 'function'); - assert.equal(typeof xs.createMimic, 'function'); assert.equal(typeof xs.never, 'function'); assert.equal(typeof xs.empty, 'function'); assert.equal(typeof xs.throw, 'function'); @@ -36,6 +35,7 @@ describe('Stream', () => { assert.equal(typeof stream.compose, 'function'); assert.equal(typeof stream.remember, 'function'); assert.equal(typeof stream.debug, 'function'); + assert.equal(typeof stream.imitate, 'function'); }); it('should be createable giving a custom producer object', (done) => { @@ -349,7 +349,7 @@ describe('Stream', () => { try { stream.addListener(listener); } catch (e) { - assert.equal(e.message, 'stream.addListener() requires all three ' + + assert.equal(e.message, 'stream.addListener() requires all three ' + 'next, error, and complete functions.'); done(); }