Create an Rx.Subject using Subject.create that allows onNext without subscription - rxjs

When creating an Rx.Subject using Subject.create(observer, observable), the Subject is so lazy. When I try to use subject.onNext without having a subscription, it doesn't pass messages on. If I subject.subscribe() first, I can use onNext immediately after.
Let's say I have an Observer, created like so:
function createObserver(socket) {
return Observer.create(msg => {
socket.send(msg);
}, err => {
console.error(err);
}, () => {
socket.removeAllListeners();
socket.close();
});
}
Then, I create an Observable that accepts messages:
function createObservable(socket) {
return Observable.fromEvent(socket, 'message')
.map(msg => {
// Trim out unnecessary data for subscribers
delete msg.blobs;
// Deep freeze the message
Object.freeze(msg);
return msg;
})
.publish()
.refCount();
}
The subject is created using these two functions.
observer = createObserver(socket);
observable = createObservable(socket);
subject = Subject.create(observer, observable);
With this setup, I'm not able to subject.onNext immediately (even if I don't care about subscribing). Is this by design? What's a good workaround?
These are actually TCP sockets, which is why I haven't relied on the super slick websocket subjects.

The basic solution, caching nexts before subscription with ReplaySubject:
I think all you wanted to do is use a ReplaySubject as your observer.
const { Observable, Subject, ReplaySubject } = Rx;
const replay = new ReplaySubject();
const observable = Observable.create(observer => {
replay.subscribe(observer);
});
const mySubject = Subject.create(replay, observable);
mySubject.onNext(1);
mySubject.onNext(2);
mySubject.onNext(3);
mySubject.subscribe(x => console.log(x));
mySubject.onNext(4);
mySubject.onNext(5);
Results in:
1
2
3
4
5
A socket implementation (example, don't use)
... but if you're looking at doing a Socket implementation, it gets a lot more complicated. Here is a working socket implementation, but I don't recommend you use it. Rather, I'd suggest that you use one of the community supported implementations either in rxjs-dom (if you're an RxJS 4 or lower) or as part of RxJS 5, both of which I've helped work on.
function createSocketSubject(url) {
let replay = new ReplaySubject();
let socket;
const observable = Observable.create(observer => {
socket = new WebSocket(url);
socket.onmessage = (e) => {
observer.onNext(e);
};
socket.onerror = (e) => {
observer.onError(e);
};
socket.onclose = (e) => {
if (e.wasClean) {
observer.onCompleted();
} else {
observer.onError(e);
}
}
let sub;
socket.onopen = () => {
sub = replay.subscribe(x => socket.send(x));
};
return () => {
socket && socket.readyState === 1 && socket.close();
sub && sub.dispose();
}
});
return Subject.create(replay, observable);
}
const socket = createSocketSubject('ws://echo.websocket.org');
socket.onNext('one');
socket.onNext('two');
socket.subscribe(x => console.log('response: ' + x.data));
socket.onNext('three');
socket.onNext('four');
Here's the obligatory JsBin

Related

RxJs - how to make observable behave like queue

I'm trying to achieve next:
private beginTransaction(): Observable() {
..
}
private test(): void {
this.beginTransaction().subscribe((): void => {
this.commitTransaction();
});
this.beginTransaction().subscribe((): void => {
this.commitTransaction();
});
}
beginTransaction can be called concurrently, but should delay the observable until first or only one beginTransaction finished.
In order words: Only one transaction can be in progress at any time.
What have I tried:
private transactionInProgress: boolean = false;
private canBeginTransaction: Subject<void> = new Subject<void>();
private bla3(): void {
this.beginTransaction().subscribe((): void => {
console.log('beginTransaction 1');
this.commitTransaction();
});
this.beginTransaction().subscribe((): void => {
console.log('beginTransaction 2');
this.commitTransaction();
});
this.beginTransaction().subscribe((): void => {
console.log('beginTransaction 3');
this.commitTransaction();
});
}
private commitTransaction(): void {
this.transactionInProgress = false;
this.canBeginTransaction.next();
}
private beginTransaction(): Observable<void> {
if(this.transactionInProgress) {
return of(undefined)
.pipe(
skipUntil(this.canBeginTransaction),
tap((): void => {
console.log('begin transaction');
})
);
}
this.transactionInProgress = true;
return of(undefined);
}
What you've asked about is pretty vague and general. Without a doubt, a more constrained scenario could probably look a whole lot simpler.
Regardless, here I create a pipeline that only lets transaction(): Observable be subscribed to once at a time.
Here's how that might look:
/****
* Represents what each transaction does. Isn't concerned about
* order/timing/'transactionInProgress' or anything like that.
*
* Here is a fake transaction that just takes 3-5 seconds to emit
* the string: `Hello ${name}`
****/
function transaction(args): Observable<string> {
const name = args?.message;
const duration = 3000 + (Math.random() * 2000);
return of("Hello").pipe(
tap(_ => console.log("starting transaction")),
switchMap(v => timer(duration).pipe(
map(_ => `${v} ${name}`)
)),
tap(_ => console.log("Ending transation"))
);
}
// Track transactions
let currentTransactionId = 0;
// Start transactions
const transactionSubj = new Subject<any>();
// Perform transaction: concatMap ensures we only start a new one if
// there isn't a current transaction underway
const transaction$ = transactionSubj.pipe(
concatMap(({id, args}) => transaction(args).pipe(
map(payload => ({id, payload}))
)),
shareReplay(1)
);
/****
* Begin a new transaction, we give it an ID since transactions are
* "hot" and we don't want to return the wrong (earlier) transactions,
* just the current one started with this call.
****/
function beginTransaction(args): Observable<any> {
return defer(() => {
const currentId = currentTransactionId++;
transactionSubj.next({id: currentId, args});
return transaction$.pipe(
first(({id}) => id === currentId),
map(({payload}) => payload)
);
})
}
// Queue up 3 transactions, each one will wait for the previous
// one to complete before it will begin.
beginTransaction({message: "Dave"}).subscribe(console.log);
beginTransaction({message: "Tom"}).subscribe(console.log);
beginTransaction({message: "Tim"}).subscribe(console.log);
Asynchronous Transactions
The current setup requires transactions to be asynchronous, or you risk losing the first one. The workaround for that is not simple, so I've built an operator that subscribes, then calls a function as soon as possible after that.
Here it is:
function initialize<T>(fn: () => void): MonoTypeOperatorFunction<T> {
return s => new Observable(observer => {
const bindOn = name => observer[name].bind(observer);
const sub = s.subscribe({
next: bindOn("next"),
error: bindOn("error"),
complete: bindOn("complete")
});
fn();
return {
unsubscribe: () => sub.unsubscribe
};
});
}
and here it is in use:
function beginTransaction(args): Observable<any> {
return defer(() => {
const currentId = currentTransactionId++;
return transaction$.pipe(
initialize(() => transactionSubj.next({id: currentId, args})),
first(({id}) => id === currentId),
map(({payload}) => payload)
);
})
}
Aside: Why Use defer?
Consider re-writting beginTransaction:
function beginTransaction(args): Observable<any> {
const currentId = currentTransactionId++;
return transaction$.pipe(
initialize(() => transactionSubj.next({id: currentId, args})),
first(({id}) => id === currentId),
map(({payload}) => payload)
);
}
In this case, the ID is set at the moment you invoke beginTransaction.
// The ID is set here, but it won't be used until subscribed
const preppedTransaction = beginTransaction({message: "Dave"});
// 10 seconds later, that ID gets used.
setTimeout(
() => preppedTransaction.subscribe(console.log),
10000
);
If transactionSubj.next is called without the initialize operator, then this problem gets even worse as transactionSubj.next would also get called 10 seconds before the observable is subscribed to (You're sure to miss the output)
The problems continue:
What if you want to subscribe to the same observable twice?
const preppedTransaction = beginTransaction({message: "Dave"});
preppedTransaction.subscribe(
value => console.log("First Subscribe: ", value)
);
preppedTransaction.subscribe(
value => console.log("Second Subscribe: ", value)
);
I would expect the output to be:
First Subscribe: Hello Dave
Second Subscribe: Hello Dave
Instead, you get
First Subscribe: Hello Dave
First Subscribe: Hello Dave
Second Subscribe: Hello Dave
Second Subscribe: Hello Dave
Because you don't get a new ID on subscribing, the two subscriptions share one ID. defer fixes this problem by not assigning an id until subscription. This becomes seriously important when managing errors in streams (letting you re-try an observable after it errors).
I am not sure I have understood the problem right, but it looks to me as concatMap is the operator you are looking for.
An example could be the following
const transactionTriggers$ = from([
't1', 't2', 't3'
])
function processTransation(trigger: string) {
console.log(`Start processing transation triggered by ${trigger}`)
// do whatever needs to be done and then return an Observable
console.log(`Transation triggered by ${trigger} processing ......`)
return of(`Transation triggered by ${trigger} processed`)
}
transactionTriggers$.pipe(
concatMap(trigger => processTransation(trigger)),
tap(console.log)
).subscribe()
You basically start from a stream of events, where each event is supposed to trigger the processing of the transaction.
Then you use processTransaction function to do whatever you have to do to process a transaction. processTransactio needs to return an Observable which emits the result of the processing when the transaction has been processed and then completes.
Then in the pipe you can use tap to do further stuff with the result of the processing, if required.
You can try the code in this stackblitz.

Re-execute async RxJS stream after delay

I'm using RxJS 6 to lazily step through iterable objects using code similar to example running below. This is working well but I'm having trouble solving my final use case.
Full code here
import { EMPTY, defer, from, of } from "rxjs";
import { delay, expand, mergeMap, repeat } from "rxjs/operators";
function stepIterator (iterator) {
return defer(() => of(iterator.next())).pipe(
mergeMap(result => result.done ? EMPTY : of(result.value))
);
}
function iterateValues ({ params }) {
const { values, delay: delayMilliseconds } = params;
const isIterable = typeof values[Symbol.iterator] === "function";
// Iterable values which are emitted over time are handled manually. Otherwise
// the values are provided to Rx for resolution.
if (isIterable && delayMilliseconds > 0) {
const iterator = values[Symbol.iterator]();
// The first value is emitted immediately, the rest are emitted after time.
return stepIterator(iterator).pipe(
expand(v => stepIterator(iterator).pipe(delay(delayMilliseconds)))
);
} else {
return from(values);
}
}
const options = {
params: {
// Any iterable object is walked manually. Otherwise delegate to `from()`.
values: ["Mary", "had", "a", "little", "lamb"],
// Delay _between_ values.
delay: 350,
// Delay before the stream restarts _after the last value_.
runAgainAfter: 1000,
}
};
iterateValues(options)
// Is not repeating?!
.pipe(repeat(3))
.subscribe(
v => {
console.log(v, Date.now());
},
console.error,
() => {
console.log('Complete');
}
);
I'd like to add in another option which will re-execute the stream, an indefinite number of times, after a delay (runAgainAfter). I'm having trouble composing this in cleanly without factoring the result.done case deeper. So far I've been unable to compose the run-again behavior around iterateValues.
What's the best approach to accomplish the use case?
Thanks!
Edit 1: repeat just hit me in the face. Perhaps it means to be friendly.
Edit 2: No, repeat isn't repeating but the observable is completing. Thanks for any help. I'm confused.
For posterity here is the full code sample for a revised edition is repeat-able and uses a consistent delay between items.
import { concat, EMPTY, defer, from, interval, of, throwError } from "rxjs";
import { delay, expand, mergeMap, repeat } from "rxjs/operators";
function stepIterator(iterator) {
return defer(() => of(iterator.next())).pipe(
mergeMap(result => (result.done ? EMPTY : of(result.value)))
);
}
function iterateValues({ params }) {
const { values, delay: delayMilliseconds, times = 1 } = params;
const isIterable =
values != null && typeof values[Symbol.iterator] === "function";
if (!isIterable) {
return throwError(new Error(`\`${values}\` is not iterable`));
}
// Iterable values which are emitted over time are handled manually. Otherwise
// the values are provided to Rx for resolution.
const observable =
delayMilliseconds > 0
? defer(() => of(values[Symbol.iterator]())).pipe(
mergeMap(iterator =>
stepIterator(iterator).pipe(
expand(v => stepIterator(iterator).pipe(delay(delayMilliseconds)))
)
)
)
: from(values);
return observable.pipe(repeat(times));
}
I'm gonna be honest, but there could be better solution for sure. In my solution, I ended up encapsulating delay logic in a custom runAgainAfter operator. Making it an independent part, that doesn't affect your code logic directly.
Full working code is here
And the code of runAgainAfter if anybody needs it:
import { Observable } from "rxjs";
export const runAgainAfter = delay => observable => {
return new Observable(observer => {
let timeout;
let subscription;
const subscribe = () => {
return observable.subscribe({
next(value) {
observer.next(value);
},
error(err) {
observer.error(err);
},
complete() {
timeout = setTimeout(() => {
subscription = subscribe();
}, delay);
}
});
};
subscription = subscribe();
return () => {
subscription.unsubscribe();
clearTimeout(timeout);
};
});
};
Hope it helps <3

Do something if RxJs subject's refCount drops to zero

I'm working on a service layer that manages subscriptions.
I provide subject-backed observables to consumers like this:
const subject = new Subject();
_trackedSubjects.push(subject);
return subject.asObservable();
Different consumers may monitor the channel, so there may be several observables attached to each subject.
I'd like to monitor the count of subject.observers and if it ever drops back to 0, do some cleanup in my library.
I have looked at refCount, but this only is available on Observable.
I'd love to find something like:
subject.onObserverCountChange((cur, prev) =>
if(cur === 0 && prev !== 0) { cleanUp(subject) }
)
Is there a way to automatic cleanup like this on a subject?
Instead of using Subject - you should probably describe setup/cleanup logic when creating observable. See the example:
const { Observable } = rxjs; // = require("rxjs")
const { share } = rxjs.operators; // = require("rxjs/operators")
const eventSource$ = Observable.create(o => {
console.log('setup');
let i = 0
const interval = setInterval(
() => o.next(i++),
1000
);
return () => {
console.log('cleanup');
clearInterval(interval);
}
});
const events$ = eventSource$.pipe(share());
const first = events$.subscribe(e => console.log('first: ', e));
const second = events$.subscribe(e => console.log('second: ', e));
setTimeout(() => first.unsubscribe(), 3000);
setTimeout(() => second.unsubscribe(), 5000);
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>

Check if publishReplay().refCount() has observers or not

I define an Observable like this:
const obs$ = Observable.create(...)
.publishReplay(1)
.refCount();
So that it puts a ReplaySubject(1) between my source Observable and all observers.
Since ReplaySubject has in its state the number of observers (via its observers array property), how is it possible to access the ReplaySubject from obs$?
I actually only need to know if obs$ has any observers or not. RxJS4 had a hasObservers() method on Subject, but it got removed in RxJS5. How can I achieve this with RxJS5?
Not sure about your usage but for my needs I created a custom operator that allowed me to transparently perform side-effects (similar to tap) based on the state of the refCount. It just does a pass-through subscription and duck-punches the sub/unsub. The callback gets the current refCount and the previous so that you can tell the state and direction. I like using an operator for this since I can insert it at any point in my stream. If you simply want a binary output for whether there are any subscriptions or not it could be easily modified for that.
const { Observable, Observer, interval } = rxjs;
const { publishReplay, refCount } = rxjs.operators;
const tapRefCount = (onChange) => (source) => {
let refCount = 0;
// mute the operator if it has nothing to do
if (typeof onChange !== 'function') {
return source;
}
// mute errors from side-effects
const safeOnChange = (refCount, prevRefCount) => {
try {
onChange(refCount, prevRefCount);
} catch (e) {
}
};
// spy on subscribe
return Observable.create((observer) => {
const subscription = source.subscribe(observer);
const prevRefCount = refCount;
refCount++;
safeOnChange(refCount, prevRefCount);
// spy on unsubscribe
return () => {
subscription.unsubscribe();
const prevRefCount = refCount;
refCount--;
safeOnChange(refCount, prevRefCount);
};
});
};
const source = interval(1000).pipe(
publishReplay(1),
refCount(),
tapRefCount((refCount, prevRefCount) => { console.log('refCount', refCount, prevRefCount > refCount ? 'down': 'up'); })
);
const firstSub = source.subscribe((x) => { console.log('first', x); });
let secondSub;
setTimeout(() => {
secondSub = source.subscribe((x) => { console.log('second', x); });
}, 1500);
setTimeout(() => {
firstSub.unsubscribe();
}, 4500);
setTimeout(() => {
secondSub.unsubscribe();
}, 5500);
<script src="https://unpkg.com/rxjs#rc/bundles/rxjs.umd.min.js"></script>
The typescript version:
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
export const tapRefCount = (
onChange: (refCount: number, prevRefCount: number) => void
) => <T>(source: Observable<T>): Observable<T> => {
let refCount = 0;
// mute the operator if it has nothing to do
if (typeof onChange !== 'function') {
return source;
}
// mute errors from side-effects
const safeOnChange = (refCount, prevRefCount) => {
try {
onChange(refCount, prevRefCount);
} catch (e) {
}
};
// spy on subscribe
return Observable.create((observer: Observer<T>) => {
const subscription = source.subscribe(observer);
const prevRefCount = refCount;
refCount++;
safeOnChange(refCount, prevRefCount);
// spy on unsubscribe
return () => {
subscription.unsubscribe();
const prevRefCount = refCount;
refCount--;
safeOnChange(refCount, prevRefCount);
};
}) as Observable<T>;
};
The Subject class has a public property called observers (see https://github.com/ReactiveX/rxjs/blob/5.5.10/src/Subject.ts#L28)
So you can use just:
const s = new Subject();
...
if (s.observers.length > 0) {
// whatever
}
Be aware that refCount returns an Observable so you won't be able to do what I mentioned above. However, you can provide your own Subject instance to publishReplay as the third argument and use s.observers on that, see http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-publishReplay

RxJS/ReactiveX Proper modules communication

I'm pretty new to Reactive Programming but already in love. However it is still hard to switch my brain to it. I'm trying to follow all recommendations as "Avoid using subjects" and "Avoid impure functions" and of course "Avoid imperative code."
What I'm finding hard to achieve is simple cross modules communications where one module can register "action"/observable and the other could subscribe and react to it. A simple message bus will probably work but this will enforce the usage of Subjects and imperative code style which I'm trying to avoid.
So here is a simple starting point I'm playing with:
// some sandbox
class Api {
constructor() {
this.actions = {};
}
registerAction(actionName, action) {
// I guess this part will have to be changed
this.actions[actionName] = action.publishReplay(10).refCount();
//this.actions[actionName].connect();
}
getAction(actionName) {
return this.actions[actionName];
}
}
const api = new Api();
// -------------------------------------------------------------------
// module 1
let myAction = Rx.Observable.create((obs) => {
console.log("EXECUTING");
obs.next("42 " + Date.now());
obs.complete();
});
api.registerAction("myAction", myAction);
let myTrigger = Rx.Observable.interval(1000).take(2);
let executedAction = myTrigger
.flatMap(x => api.getAction("myAction"))
.subscribe(
(x) => { console.log(`executed action: ${x}`); },
(e) => {},
() => { console.log("completed");});
// -------------------------------------------------------------------
// module 2
api.getAction("myAction")
.subscribe(
(x) => { console.log(`SECOND executed action: ${x}`); },
(e) => {},
() => { console.log("SECOND completed");});
So currently at the moment the second module subscribes it "triggers" the "myAction" Observable. And in a real life scenario that could be an ajax call. Is there any way to make all subscribers delay/wait until "myAction" is called properly from module1? And again - its easy to do it using subjects but I'm trying to do it following recommended practices.
If I understand you correctly, you want to make the sure that, if you call the api.getAction, you want next values in that observable to wait till the call to the getAction completes. Before handling other values.
This is something you can achieve quite easily using the concatMap. ConcatMap will take a function that returns an observable (in your case the call to the getAction). ConcatMap will wait to start handling the next value, until the observable returned in the function completes.
So if you change your code like this, it should work (if I understood correctly).
let executedAction = myTrigger
.concatMap(x => api.getAction("myAction"))
.subscribe(
(x) => { console.log(`executed action: ${x}`); },
(e) => {},
() => { console.log("completed");});
If myTrigger has a new value, it will not be handled until the observable returned from api.getAction completes.
So here is a much simpler solution than the one I thought. With simply using 2 observables. Similar effect could be achieved with schedulers and subscribeOn.
// some sandbox
class Action {
constructor(name, observable) {
this.name = name;
this.observable = observable;
this.replay = new Rx.ReplaySubject(10);
}
}
function actionFactory(action, param) {
return Rx.Observable.create(obs => {
action.observable
.subscribe(x => {
obs.next(x);
action.replay.next(x);
}, (e) => {}, () => obs.complete);
});
}
class Api {
constructor() {
this.actions = {};
}
registerAction(actionName, action) {
let generatedAction = new Action(actionName, action);
this.actions[actionName] = generatedAction;
return actionFactory.bind(null, generatedAction);
}
getAction(actionName) {
return this.actions[actionName].replay;
}
}
const api = new Api();
// -------------------------------------------------------------------
// module 1
let myAction = Rx.Observable.create((obs) => {
obs.next("42 " + Date.now());
obs.complete();
});
let myRegisteredAction$ = api.registerAction("myAction", myAction);
let myTrigger = Rx.Observable.interval(1000).take(1).delay(1000);
let executedAction = myTrigger
.map(x => { return { someValue: x} })
.concatMap(x => myRegisteredAction$(x))
.subscribe(
(x) => { console.log(`MAIN: ${x}`); },
(e) => { console.log("error", e)},
() => { console.log("MAIN: completed");});
// -------------------------------------------------------------------
// module 2
var sub = api.getAction("myAction")
.subscribe(
(x) => { console.log(`SECOND: ${x}`); },
(e) => {console.log("error : " + e)},
() => { console.log("SECOND: completed");});

Resources