How to create an Observable that only fires when it has subscribers, and provides the latest value to new subscribers immediately - javascript

I'm trying to create a stream/observable that...
Only outputs events when it has subscribers
Provides any new subscribers with the latest value.
The concrete case is that I need an observable that makes an Async API call whenever a particular event happens, but only if it has subscribers. I'm trying to avoid unnecessary API calls.
I've managed to create a stream that only fires when it has subscribers like this...
let dataStream = Rx.Observable
.interval(1000) // Fire an event every second
.singleInstance() // Only do something when we have subscribers
.startWith(null) // kick start as soon as something subscribes
.flatMapLatest(interval => SomeAPI.someDataGet()) // get data, returns a promise
And this works. If I console.log(...) in the SomeAPI.someDataGet method, I only see it firing when the stream has subscribers. And my implementation looks really nice because I do this to subscribe and unsubscribe which fits in very nicely with React component lifecycle methods.
let sub1;
sub1 = dataStream.subscribe(x => console.log('sub1', x));
sub1.dispose();
I also want any new subscribers to receive the latest value the instant they subscribe. This is where I'm struggling. If I do this...
let sub1, sub2;
sub1 = dataStream.subscribe(x => console.log('sub1', x));
setTimeout( () => {
sub2 = dataStream.subscribe(x => console.log('sub2', x));
}, 1500)
...I don't see the console.log for sub2 until the next interval.
If my understanding is correct. I need a Hot Observable. So I have tried to create a stream like this...
let dataStream = Rx.Observable
.interval(1000) // Fire an event every second
.singleInstance() // Only do something when we have subscribers
.startWith(null) // kick start as soon as something subscribes
.flatMapLatest(interval => SomeAPI.someDataGet()) // get data
.publish() // Make this a hot observable;
Which as I understand it, should make dataStream a hot observable.
However, in my tests the second subscription still doesn't receive data until the next interval. In addition, this would introduce the requirement to connect and disconnect the dataStream when subscribing which is something I would like to avoid if possible.
I'm brand new to RxJS and I would not be surprised if I've misunderstood what's happening here.

Instead of .publish(), use .shareReplay(1).

Related

RxJS - initial state and updates

I need to obtain data from websocket and I want to use RxJS to do so.
There is a websocket 1 for the latest initial data (~1000 records) and websocket 2 for the incremental updates.
I have created two observables:
initalState$ that goes to websocket 1 and fetches the initial data and then completes.
updateEvent$ that goes to websocket 2 and continuously receives updates.
My initial implementation was:
initialState.subscribe(initialData=> {
console.log(initialData);
updateEvent.subscribe(updateEvent => {
console.log(updateEvent);
});
});
The issue that I'm facing is that there is a gap after fetching the initalState and receiving the first update (updateEvent).
(I might lose update that happens after I fetch the initial data and before the subscribe).
Is there some practical way that I can create a new Observer that subscribes to both of my observers at the same time and buffer the updateEvent observer until the initalState completes and then have them in the right order "initial data first" then "updates" ?
Basically making the initialState just the "first" update, but making sure there aren't any missing updates after that.
It looks like you could achieve what you need by using buffer for the second websocket stream until the first one emits. Although, this chain gets a little more complicated because you want to start receiving values only after the first stream emits.
const initialStateShared = initialState.pipe(share());
const updateEventShared = updateEvent.pipe(share());
merge(
initialStateShared,
updateEventShared.pipe( // Buffer the second stream but only once
buffer(initialStateShared),
take(1),
),
updateEventShared.pipe( // Updates from the second stream will be buffered first and then continue comming from here
skipUntil(initialStateShared),
)
).subscribe(...);
If I am understanding it correctly what you want is to trigger both of the requests simultaneously and subscribe to them only if both are already available. I think you are looking for the combineLatest operator.
combineLatest([initialState$, updateEvent$]).subscribe(([initialState, updateEvent] => {
console.log({initialState, updateEvent});
}));
This way the combined observable will wait for both initialState$ and updateEvent$ to have emitted something and after that it will trigger emits if either of the combined observables emits something. See https://www.learnrxjs.io/operators/combination/combinelatest.html for more information.
Note: You should prevent doing a subscribe in another subscribe. It is often a code smell for doing something wrong.

What is the difference between Observable and a Subject in rxjs?

I was going through this blog and reading about Observables and couldn't figure out the difference between the Observable and a Subject.
In stream programming there are two main interfaces: Observable and Observer.
Observable is for the consumer, it can be transformed and subscribed:
observable.map(x => ...).filter(x => ...).subscribe(x => ...)
Observer is the interface which is used to feed an observable source:
observer.next(newItem)
We can create new Observable with an Observer:
var observable = Observable.create(observer => {
observer.next('first');
observer.next('second');
...
});
observable.map(x => ...).filter(x => ...).subscribe(x => ...)
Or, we can use a Subject which implements both the Observable and the Observer interfaces:
var source = new Subject();
source.map(x => ...).filter(x => ...).subscribe(x => ...)
source.next('first')
source.next('second')
Observables are unicast by design and Subjects are multicast by design.
If you look at the below example, each subscription receives the different values as observables developed as unicast by design.
import {Observable} from 'rxjs';
let obs = Observable.create(observer=>{
observer.next(Math.random());
})
obs.subscribe(res=>{
console.log('subscription a :', res); //subscription a :0.2859800202682865
});
obs.subscribe(res=>{
console.log('subscription b :', res); //subscription b :0.694302021731573
});
This could be weird if you are expecting the same values on both the subscription.
We can overcome this issue using Subjects. Subjects is similar to event-emitter and it does not invoke for each subscription. Consider the below example.
import {Subject} from 'rxjs';
let obs = new Subject();
obs.subscribe(res=>{
console.log('subscription a :', res); // subscription a : 0.91767565496093
});
obs.subscribe(res=>{
console.log('subscription b :', res);// subscription b : 0.91767565496093
});
obs.next(Math.random());
Both of the subscriptions got the same output value!
Observables
They are cold: Code gets executed when they have at least a single observer.
Creates copy of data: Observable creates copy of data for each observer.
Uni-directional: Observer can not assign value to observable(origin/master).
The code will run for each observer . If its a HTTP call, it gets called for each observer.
if its a service we want to share among all the components, it wont have latest result all new subscribers will still subscribe to same observable and get value from scratch
Unicast means can emit values from the observable not from any other component.
Subject
They are hot: code gets executed and value gets broadcast even if there is no observer.
Shares data: Same data get shared between all observers.
bi-directional: Observer can assign value to observable(origin/master).
If are using using subject then you miss all the values that are broadcast before creation of observer. So here comes Replay Subject
multicast, can cast values to multiple subscribers and can act as both subscribers and emmitter
I found the accepted answer slightly confusing!
An Observer isn't the interface for feeding an Observable source, it's the interface for observing an Observable source... which makes more sense from the name, right?
So, the reason that:
var observable = Observable.create(observer => {
observer.next('first');
observer.next('second');
...
});
works - creating an observable which emits 'first' then 'second' - is that the argument to Observable.create(...) is a subscribe function, it basically defines which Observer events will happen on a direct Observer of that Observable.
If you want to go into it a little bit further again, it's important to understand that the subscribe function isn't directly called on the Observer object when you subscribe, instead it's mediated by a Subscription object which can enforce correct observable rules, e.g. that an Observable will never emit a new value after observer.complete() has been called, even if your subscribe function looks as if it would.
REF: http://reactivex.io/rxjs/manual/overview.html#creating-observables
A Subject is both an Observable and an Observer and once again it looks just like the Observer interface is the way to 'feed' events to the Subject. But it's easier to understand the naming if you realise that a Subject is a bit like an Observable with the equivalent of the subscribe function (i.e. where you define what events will happen to things observing it) sitting there right on the object, even after it has been created. So, you call Observer methods on the Subject to define what Observer events will happen on things observing it! 😊 (And again, there are intermediate objects involved, to make sure that you can only do legal sequences of things.)
REF: http://reactivex.io/rxjs/manual/overview.html#subject
See rxjs document (more information and examples there):
http://reactivex.io/rxjs/manual/overview.html#subject
What is a Subject? An RxJS Subject is a special type of Observable that allows values to be multicasted to many Observers. While plain Observables are unicast (each subscribed Observer owns an independent execution of the Observable), Subjects are multicast.
A Subject is like an Observable, but can multicast to many Observers. Subjects are like EventEmitters: they maintain a registry of many listeners.
and code, Subject extending Observable: https://github.com/ReactiveX/rxjs/blob/master/src/internal/Subject.ts#L22
/**
* #class Subject<T>
*/
export class Subject<T> extends Observable<T> implements SubscriptionLike {
//...
}
Imagine if you have a stream of data coming into your application like in a websocket connection. You want a way to handle it. There is a few solution:
1. normal ajax request:
This solution is not viable because it is
not applicable to process push data. It is more of a pull then a
push.
2. Promise:
Also not good because you have to trigger them and
they can only retrieve once. Also more of a pull then a push.
So in order to retrieve this data, in the old time, we do a long-polling. Which is where we set an interval function to retrieve that stream of data every 1 minute for an example. Though it works, it actually burdening resources like CPU and memory.
But now with option no 3,
3. Observable: You can subscribe and let the stream of data to come
in non-stop until the function complete has been called.
Cool right ? But then there is another problem. What if you want to observe incoming data only once somewhere in your application. But you want to use that data simultaneously around your application when the data arrived. That is when and where you use Subject.
You place subject.subscribe() at places you want to use throughout your application. When the data arrived, places where there is subject.subscribe() will process them simultaneously. But the observer must subscribe with the subject as its argument like this.
observer.subscribe(subject).
Example application is when you want to build a notification alert.
You cannot have multiple subscription of the same observable because chances are, each subscribers will received different input data. But with subject, all that subscribe() through subject will be retrieving the same data.
Another analogy is through magazine subscription. Each subscribers will received the magazine with their name on it. So, different subscription = different receiver name.(Normal Observable)
But when you share with your friends, all of your friend would receive the same magazine with only your name on it.(Normal Observable with Subject)
This guy explain it very well with code example. You can check it out at https://javascript.tutorialhorizon.com/2017/03/23/rxjs-subject-vs-observable/
Hopefully this answer helps.
Briefly,
subject: you can send to it and receive from it.
Observable: you can receive from it only.
In another words,
In subject you can subscribe to it and you can use it to broadcast to other subscribers any time and anywhere in code.
whilst,
in observable you can subscribe to it only (you can't use it to broadcast data after it have been initialized).
The only place you can broadcast data from observable is inside its constructor.
Observable can inform only one observer, while Subject can inform multiple observers.
From another perspective, it is good to note that the subscription to an Observable re-execute the Observable function. This can lead performance issue if the data source is a service for instance.
If you want several subscribers to get the same value, you may need a Subject.
For this, make sure that your subscription is set before the Subject subscribed to the data source. Otherwise, your process would be stuck.
More details here: https://javascript.tutorialhorizon.com/2017/03/23/rxjs-subject-vs-observable/
Observable:
Only the Observable knows how and when the events are triggered on the observable. i.e the next() method has to be called only inside the instantiated constructor. Also, on subscribing each time, a separate observer is created and calls next() method using particular observer inside constructor only, in the following example subscriber itself is the observer and it is subscribed when the instantiated constructor gets executed.
Ex:
import { Observable } from 'rxjs';
const observable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
setTimeout(() => {
subscriber.next(3);
}, 1000);
});
Subject:
Here next() method can be used by subject anywhere outside the constructor. Also, when next() method is called before subscribing, the particular event will be missed. Hence next() method has to be called only after subscribing.
Ex:
import { Subject } from 'rxjs';
const subject = new Subject<number>();
subject.next(1); // this is missed
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
subject.next(2);

Making a lazy, cached observable that only execute the source once

I'm trying to use an rxjs observable to delegate, but share, a piece of expensive work across the lifetime of an application.
Essentially, something like:
var work$ = Observable.create((o) => {
const expensive = doSomethingExpensive();
o.next(expensive);
observer.complete();
})
.publishReplay(1)
.refCount();
Now, this works fine and does exactly what I want, except for one thing: if all subscribers unsubscribe, then when the next one subscribes, my expensive work happens again. I want to keep it.
now, I could use a subject, or I could remove the refCount() and use connect manually (and never disconnect). But that would make the expensive work happen the moment I connect, not the first time a subscriber tries to consume work$.
Essentially, I want something akin to refCount that only looks at the first subscription to connect, and never disconnect. A "lazy connect".
Is such a thing possible at all?
How does publishReplay() actually work
It internally creates a ReplaySubject and makes it multicast compatible. The minimal replay value of ReplaySubject is 1 emission. This results in the following:
First subscription will trigger the publishReplay(1) to internally subscribe to the source stream and pipe all emissions through the ReplaySubject, effectively caching the last n(=1) emissions
If a second subscription is started while the source is still active the multicast() will connect us to the same replaySubject and we will receive all next emissions until the source stream completes.
If a subscription is started after the source is already completed the replaySubject has cached the last n emissions and it will only receive those before completing.
const source = Rx.Observable.from([1,2])
.mergeMap(i => Rx.Observable.of('emission:'+i).delay(i * 100))
.do(null,null,() => console.log('source stream completed'))
.publishReplay(1)
.refCount();
// two subscriptions which are both in time before the stream completes
source.subscribe(val => console.log(`sub1:${val}`), null, () => console.log('sub1 completed'));
source.subscribe(val => console.log(`sub2:${val}`), null, () => console.log('sub2 completed'));
// new subscription after the stream has completed already
setTimeout(() => {
source.subscribe(val => console.log(`sub_late-to-the-party:${val}`), null, () => console.log('sub_late-to-the-party completed'));
}, 500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>

asynchronously add epic to middleware in redux-observable

I'm trying to evaluate redux-observable. Just looking through the doc and I'm trying to get the async epic loading thing going. I created a fork of the jsbin from the docs which basically attempts to add the async usage of the BehaviorSubject stuff.
http://jsbin.com/bazoqemiqu/edit?html,js,output
In that 'PING PONG' example, I added an 'OTHER' action and then use BehaviorSubject.next (as described in the docs) to add that epic. However, when I run the example, what happens is that the PING action is fired, followed by an endless stream of 'OTHER' actions, but never the PONG action. To see this, I added the reduxLogger. View it in the dev tools console as the jsbin console doesn't render it correctly.
My question is what am I doing wrong? Why does the PONG action never get dispatched?
Your otherEpic is an infinite "loop" (over time)
const otherEpic$ = action$ =>
action$
.delay(1000)
.mapTo({ type: OTHER });
This epic has the behavior "when any action at all is received, wait 1000ms and then emit another action of type OTHER". And since the actions your Epics emit go through the normal store.dispatch cycle like any other action, that means after the first PING is received, it will emit an OTHER after 1000ms, which will then be recursively received by the same epic again, wait another 1000ms and emit another OTHER, repeat forever.
I'm not sure if this was known, but wanted to point it out.
You next() into the BehaviorSubject of epic$ before your rootEpic has started running/been subscribed to it.
BehaviorSubjects will keep the last value emitted and provide that immediately when someone subscribes. Since your rootEpic has not yet been called and subscribed to the by the middleware, you're replacing the initial value, so only the otherEpic is emitted and ran through the epic$.mergeMap stuff.
In a real application with async/bundle splitting, when you would call epic$.next(newEpic) should always be after the middleware has subscribed to your rootEpic and received the initial epic you provided to your BehaviorSubject.
Here's a demo of that working: http://jsbin.com/zaniviz/edit?js,output
const epic$ = new BehaviorSubject(combineEpics(epic1, epic2, ...etc));
const rootEpic = (action$, store) =>
epic$.mergeMap(epic => {console.log(epic)
return epic(action$, store)
});
const otherEpic = action$ =>
action$.ofType(PONG)
.delay(1000)
.mapTo({ type: OTHER });
const epicMiddleware = createEpicMiddleware(rootEpic);
const store = createStore(rootReducer,
applyMiddleware(loggerMiddleware, epicMiddleware)
);
// any time AFTER the epicMiddleware
// has received the rootEpic
epic$.next(otherEpic);
The documentation says "sometime later" in the example, which I now see isn't clear enough. I'll try and clarify this further.
You may also find this other question on async loading of Epics useful if you're using react-router with Webpack's require.enquire() splitting.
Let me know if I can clarify any of these further 🖖

Observable instance emit without an observer (or subscriber ?)

I am using observable in Angular2. As I know so far, each Observable instance come with an observer(1:1), and when we emit something with observer.next(value) we can get that value with observable.subscribe((value) => {}).
var observable = Observable.create(observer => {
observer.next(value);
}
.map(value=>{})
.catch(...)
observable.subscribe(value => {
console.log(value);
})
How can I emit value without knowing the corresponding observer, because I want to emit value outside create function. One possible solution is save observer into some global variable but I think an observable should be enough. Any suggestion for this ??
You're mixing multiple things together. Observables are not in 1:1 relation with Observers (more precisely it's 1:N). If you want to be able to manually emit values you need a Subject which acts as an Observable and an Observer at the same time. Practically this means you can call its next() method and it'll propage the value to all its subscribers (Observers).
For example consider the following code in TypeScript:
import {Subject} from 'rxjs';
let source = new Subject();
source.subscribe(val => console.log('Observer 1:', val));
source.subscribe(val => console.log('Observer 2:', val));
source.next(42);
source.next('test');
This will print to console:
Observer 1: 42
Observer 2: 42
Observer 1: test
Observer 2: test
See live demo: http://plnkr.co/edit/gWMFMnPlLJVDC1pQi8pH?p=preview
Read more:
http://reactivex.io/intro.html
https://github.com/Reactive-Extensions/RxJS#resources
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md
Be aware that Observable.create() is a very different animal. It takes as a parameter a function that is called every time a new Observer subscribes. That's why it take the newly subscribed Observer as an argument. In this function you can for example call next() method on the Observer to send it some default value that all subscribes need to receive.
So you probably want to use Subject instead of Observable.create().

Categories

Resources