I have tried to unsubscribe within the subscribe method. It seems like it works, I haven't found an example on the internet that you can do it this way.
I know that there are many other possibilities to unsubscribe the method or to limit it with pipes. Please do not suggest any other solution, but answer why you shouldn't do that or is it a possible way ?
example:
let localSubscription = someObservable.subscribe(result => {
this.result = result;
if (localSubscription && someStatement) {
localSubscription.unsubscribe();
}
});
The problem
Sometimes the pattern you used above will work and sometimes it won't. Here are two examples, you can try to run them yourself. One will throw an error and the other will not.
const subscription = of(1,2,3,4,5).pipe(
tap(console.log)
).subscribe(v => {
if(v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
Error: Cannot access 'subscription' before initialization
Something similar:
const subscription = of(1,2,3,4,5).pipe(
tap(console.log),
delay(0)
).subscribe(v => {
if (v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
This time you don't get an error, but you also unsubscribed before the 5 was emitted from the source observable of(1,2,3,4,5)
Hidden Constraints
If you're familiar with Schedulers in RxJS, you might immediately be able to spot the extra hidden information that allows one example to work while the other doesn't.
delay (Even a delay of 0 milliseconds) returns an Observable that uses an asynchronous scheduler. This means, in effect, that the current block of code will finish execution before the delayed observable has a chance to emit.
This guarantees that in a single-threaded environment (like the Javascript runtime found in browsers currently) your subscription has been initialized.
The Solutions
1. Keep a fragile codebase
One possible solution is to just ignore common wisdom and continue to use this pattern for unsubscribing. To do so, you and anyone on your team that might use your code for reference or might someday need to maintain your code must take on the extra cognitive load of remembering which observable use the correct scheduler.
Changing how an observable transforms data in one part of your application may cause unexpected errors in every part of the application that relies on this data being supplied by an asynchronous scheduler.
For example: code that runs fine when querying a server may break when synchronously returned a cashed result. What seems like an optimization, now wreaks havoc in your codebase. When this sort of error appears, the source can be rather difficult to track down.
Finally, if ever browsers (or you're running code in Node.js) start to support multi-threaded environments, your code will either have to make do without that enhancement or be re-written.
2. Making "unsubscribe inside subscription callback" a safe pattern
Idiomatic RxJS code tries to be schedular agnostic wherever possible.
Here is how you might use the pattern above without worrying about which scheduler an observable is using. This is effectively scheduler agnostic, though it likely complicates a rather simple task much more than it needs to.
const stream = publish()(of(1,2,3,4,5));
const subscription = stream.pipe(
tap(console.log)
).subscribe(x => {
if(x === 4) subscription.unsubscribe();
});
stream.connect();
This lets you use a "unsubscribe inside a subscription" pattern safely. This will always work regardless of the scheduler and would continue to work if (for example) you put your code in a multi-threaded environment (The delay example above may break, but this will not).
3. RxJS Operators
The best solutions will be those that use operators that handle subscription/unsubscription on your behalf. They require no extra cognitive load in the best circumstances and manage to contain/manage errors relatively well (less spooky action at a distance) in the more exotic circumstances.
Most higher-order operators do this (concat, merge, concatMap, switchMap, mergeMap, ect). Other operators like take, takeUntil, takeWhile, ect let you use a more declarative style to manage subscriptions.
Where possible, these are preferable as they're all less likely to cause strange errors or confusion within a team that is using them.
The examples above re-written:
of(1,2,3,4,5).pipe(
tap(console.log)
first(v => v === 4)
).subscribe();
It's working method, but RxJS mainly recommend use async pipe in Angular. That's the perfect solution. In your example you assign result to the object property and that's not a good practice.
If you use your variable in the template, then just use async pipe. If you don't, just make it observable in that way:
private readonly result$ = someObservable.pipe(/...get exactly what you need here.../)
And then you can use your result$ in cases when you need it: in other observable or template.
Also you can use pipe(take(1)) or pipe(first()) for unsubscribing. There are also some other pipe methods allowing you unsubscribe without additional code.
There are various ways of unsubscribing data:
Method 1: Unsubscribe after subscription; (Not preferred)
let localSubscription = someObservable.subscribe(result => {
this.result = result;
}).unsubscribe();
---------------------
Method 2: If you want only first one or 2 values, use take operator or first operator
a) let localSubscription =
someObservable.pipe(take(1)).subscribe(result => {
this.result = result;
});
b) let localSubscription =
someObservable.pipe(first()).subscribe(result => {
this.result = result;
});
---------------------
Method 3: Use Subscription and unsubscribe in your ngOnDestroy();
let localSubscription =
someObservable.subscribe(result => {
this.result = result;
});
ngOnDestroy() { this.localSubscription.unsubscribe() }
----------------------
Method 4: Use Subject and takeUntil Operator and destroy in ngOnDestroy
let destroySubject: Subject<any> = new Subject();
let localSubscription =
someObservable.pipe(takeUntil(this.destroySubject)).subscribe(result => {
this.result = result;
});
ngOnDestroy() {
this.destroySubject.next();
this.destroySubject.complete();
}
I would personally prefer method 4, because you can use the same destroy subject for multiple subscriptions if you have in a single page.
Related
Since browsers' DOM API works via an event loop and to listen for changes on the dom we register event handlers.
What does RXJS behind the scene implement for subscribers to listen for changes in data.
I could be entirely missing the plot here. (I use them day to day but would like to know under the hood how they work with the DOM APIs).
I understand observer pattern but that is a design abstraction, how the core RXJS operates with the DOM is what I am looking for.
The straight forward way would take a look at source code: fromEvent as you want to see DOM interaction and schedulers for DOM API(setInterval, for example)
Observables are just functions. They do not provide anything special under the hood, they just offer whatever the Javascript platform, browser or node, offers with a different powerful paradigm. The power of RxJS lays in the rich set of functions and operators which can be composed in transformation pipelines. This is where the magic of Observables comes from.
Consider the following example which wraps the readline function provided by the fs module of Node.
const readLineObs = (filePath: string): Observable<string> => {
return Observable.create(
(observer: Observer<string>): TeardownLogic => {
const rl = readline.createInterface({
input: fs.createReadStream(filePath),
crlfDelay: Infinity
});
rl.on("line", (line: string) => {
observer.next(line);
});
rl.on("close", () => {
observer.complete();
});
}
);
};
This code relies on Node apis to do the work of reading a file line by line, but offers the result as an Observable. The advantage is that you can use the Observable operators to process the results, like in the following example
readLinesObs('my_path_to_file').pipe(
filter(line => line[0] === 'A'), // take only the lines which start with A
map((line, i) => i + '_' + line), // add a line number at the start of the line
take(10), // just take the first 10 lines
// do all other types of transformations you may need
)
I have the following epic I use in my application to handle api requests:
action$ => {
return action$.ofType(actions.requestType)
.do(() => console.log('handled epic ' + actions.requestType))
.switchMap((action) => (
Observable.create((obs) => {
obs.next({ type: type, value: action.value, form: action.form });
})
.debounceTime(250)
.switchMap((iea) => (
Observable.ajax(ajaxPost(url(iea.value), body ? body(iea.value) : action.form))
.mergeMap(payload => {
return Observable.merge(
Observable.of(actions.success(payload)),
/* some other stuff */
);
})
.catch(payload => {
return [actions.failure(payload)];
})
))
))
.takeUntil(action$.filter((a) => (a.type === masterCancelAction))
.repeat();
};
Basically, any time I perform an api request, I dispatch a request action. If I dispatch another request quickly, the previous one is ignored using debounceTime. Additionally, the request can be cancelled using the masterCancelAction and when cancelled repeat() restarts the epic. This epic works as intended in all cases expect one.
The failure case occurs when a user uses the browser back during a request. In this case I fire the masterCancelAction to the request. However, on the same execution context as a result from the masterCancelAction, another request action dispatches to perform a new request on the same epic, but the api request does not occur (the console.log does occur though) as if there was no repeat(). In other cases where cancels occur, the next request is not invoked from the same execution context and it works fine, so it seems in this case my code does not give repeat a chance to restart the epic?
A dirty workaround I found was to use setTimeout(dispatch(action), 0) on the request that dispatches after the cancellation. This seems to allow repeat() to execute. I tried passing different schedulers into repeat, but that didn't seem to help. Also, attaching takeUntil and repeat into my inner switchMap solves the problem, but then other cases where my next request does not execute in the same call stack fail.
Is there a way I can solve this problem without using setTimeout? Maybe it is not a repeat related problem, but it seems to be the case.
Using rxjs 5.0.3 and redux-observable 0.14.1.
The issue is not 100% clear without something like a jsbin to see what you mean, but I do see some general issues that might help:
Anonymous Observable never completes
When creating a custom anonymous Observable it's important to call observer.complete() if you do indeed want it to complete. In most cases, not doing so will cause the subscription to be a memory leak and might also other strange behaviors
Observable.create((observer) => {
observer.next({ type: type, value: action.value, form: action.form });
observer.complete();
})
Observable.of would have been equivalent:
Observable.of({ type: type, value: action.value, form: action.form })
However, it's not clear why this was done as the values it emits are in captured in scope.
debounceTime in this case does not debounce, it delays
Since the anonymous observable it's applied to only ever emits a single item, debounceTime will act just as a regular .delay(250). I'm betting you intended instead to debounce actions.requestType actions, in which case you'd need to apply your debouncing outside the switchMap, after the action$.ofType(actions.requestType).
Observable.of accepts any number of arguments to emit
This is more of a "did you know?" rather than an issue, but I noticed you're merging your of and /* some other actions */ I assume would be other of observables merged in. Instead, you can just return a single of and pass the actions as arguments.
Observable.of(
actions.success(payload),
/* some other actions */
actions.someOtherOne(),
actions.etc()
);
Also, when you find yourself emitting multiple actions synchronously like this, consider whether your reducers should be listening for the same, single action instead of having two or more. Sometimes this wouldn't make sense as you want them to have completely unrelated actions, just something to keep in mind that people often forget--that all reducers receive all actions and so multiple reducers can change their state from the same action.
.takeUntil will stop the epic from listening for future actions
Placing the takeUntil on the top-level observable chain causes the epic to stop listening for action$.ofType(actions.requestType), which is why you added the .repeat() after. This might work in some cases, but it's inefficient and can cause other hard to realize bugs. Epics should be thought of instead as sort of like sidecar processes that usually "start up" with the app and then continue listening for a particular action until the app "shuts down" aka the user leaves the app. They aren't actually processes, it's just helpful to conceptually think of them this way as an abstraction.
So each time it matches its particular action it then most often will switchMap, mergeMap, concatMap, or exhaustMap into some side effect, like an ajax call. That inner observable chain is what you want to make cancellable. So you'd place your .takeUntil on it, at the appropriate place in the chain.
Summary
As mentioned, it's not clear what you intended to do and what the issue is, without a more complete example like a jsbin. But strictly based on the code provided, this is my guesstimate:
const someRequestEpic = action$ => {
return action$.ofType(actions.requestType)
.debounceTime(250)
.do(() => console.log('handled epic ' + actions.requestType))
.switchMap((action) =>
Observable.ajax(ajaxPost(url(action.value), body ? body(action.value) : action.form))
.takeUntil(action$.ofType(masterCancelAction))
.mergeMap(payload => {
return Observable.of(
actions.success(payload),
/* some other actions */
...etc
);
})
.catch(payload => Observable.of(
actions.failure(payload)
))
);
};
Check out the Cancellation page in the redux-observable docs.
If this is a bit confusing, I'd recommend digging a bit deeper into what Observables are and what an "operator" is and does so that it doesn't feel magical and where you should place an operator makes more sense.
Ben's post on Learning Observable by Building Observable is a good start.
I'm working on something that is recording data coming from a queue. It was easy enough to process the queue into an Observable so that I can have multiple endpoints in my code receiving the information in the queue.
Furthermore, I can be sure that the information arrives in order. That bit works nicely as well since the Observables ensure that. But, one tricky bit is that I don't want the Observer to be notified of the next thing until it has completed processing the previous thing. But the processing done by the Observer is asynchronous.
As a more concrete example that is probably simple enough to follow. Imagine my queue contains URLs. I'm exposing those as an Observable in my code. The I subscribe an Observer whose job is to fetch the URLs and write the content to disk (this is a contrived example, so don't take issue with these specifics). The important point is that fetching and saving are async. My problem is that I don't want the observer to be given the "next" URL from the Observable until they have completed the previous processing.
But the call to next on the Observer interface returns void. So there is no way for the Observer to communicate back to me that has actually completed the async task.
Any suggestions? I suspect there is probably some kind of operator that could be coded up that would basically withhold future values (queue them up in memory?) until it somehow knew the Observer was ready for it. But I was hoping something like that already existed following some established pattern.
similar use case i ran into before
window.document.onkeydown=(e)=>{
return false
}
let count=0;
let asyncTask=(name,time)=>{
time=time || 2000
return Rx.Observable.create(function(obs) {
setTimeout(function() {
count++
obs.next('task:'+name+count);
console.log('Task:',count ,' ', time, 'task complete')
obs.complete();
}, time);
});
}
let subject=new Rx.Subject()
let queueExec$=new Rx.Subject()
Rx.Observable.fromEvent(btnA, 'click').subscribe(()=>{
queueExec$.next(asyncTask('A',4000))
})
Rx.Observable.fromEvent(btnB, 'click').subscribe(()=>{
queueExec$.next(asyncTask('B',4000))
})
Rx.Observable.fromEvent(btnC, 'click').subscribe(()=>{
queueExec$.next(asyncTask('C',4000))
})
queueExec$.concatMap(value=>value)
.subscribe(function(data) {
console.log('onNext', data);
},
function(error) {
console.log('onError', error);
},function(){
console.log('completed')
});
What you describe sounds like "backpressure". You can read about it in RxJS 4 documentation https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/backpressure.md. However this is mentioning operators that don't exist in RxJS 5. For example have a look at "Controlled Observables" that should refer to what you need.
I think you could achieve the same with concatMap and an instance of Subject:
const asyncOperationEnd = new Subject();
source.concatMap(val => asyncOperationEnd
.mapTo(void 0)
.startWith(val)
.take(2) // that's `val` and the `void 0` that ends this inner Observable
)
.filter(Boolean) // Always ignore `void 0`
.subscribe(val => {
// do some async operation...
// call `asyncOperationEnd.next()` and let `concatMap` process another value
});
Fro your description it actually seems like the "observer" you're mentioning works like Subject so it would make maybe more sense to make a custom Subject class that you could use in any Observable chain.
Isn't this just concatMap?
// Requests are coming in a stream, with small intervals or without any.
const requests=Rx.Observable.of(2,1,16,8,16)
.concatMap(v=>Rx.Observable.timer(1000).mapTo(v));
// Fetch, it takes some time.
function fetch(query){
return Rx.Observable.timer(100*query)
.mapTo('!'+query).startWith('?'+query);
}
requests.concatMap(q=>fetch(q));
https://rxviz.com/v/Mog1rmGJ
If you want to allow multiple fetches simultaneously, use mergeMap with concurrency parameter.
With the pausable operator not implemented in RxJS v5, is there a better way to create a pausable interval? The code below works, but does so by keeping track of the last emitted value as an offset. It seems like there should be a better way...
const source = Rx.Observable.interval(100).share()
const offset = new Rx.BehaviorSubject(0)
let subscription;
let currentValue;
function start() {
subscription = source
.subscribe(i => {
currentValue = i + offset.value
})
}
function pause() {
source.take(1).subscribe(i => offset.next(i + offset.value))
subscription.unsubscribe()
}
The share() operator is an alias for .publish().refCount(). The refCount() means that the observable will clean itself up when no other subscribers to it exist. Because you're unsubscribing from source, it will clean itself up, then restart when subscribed to again. Use publish() with connect() instead. Here's the code:
const source = Observable.interval(100).publish();
source.connect();
// Start with false, change to true after 200ms, then false again
// after another 200ms
const pauser = Observable.timer(200)
.mapTo(true)
.concat(Observable.timer(200).mapTo(false))
.startWith(false);
const pausable = pauser
.switchMap(paused => (paused ? Observable.never() : source))
.take(10);
pausable.subscribe(x => console.log(x));
See this jsbin for a running example: http://jsbin.com/jomusiy/3/edit?js,console.
There's no general purpose way to do this. It depends on what exactly you mean by pausing and also what you're pausing. (Do you want to just stop emitting and then start again, do you buffer and drain, or do you effectively need to add a delay to the upstream, but otherwise preserve the distribution of upstream values in time?)
I have a way to do this in an efficient manner when the upstream is specifically a timer, as it is in your example. It is the answer to my own question here.
RxJS (5.0rc4): Pause and resume an interval timer
This one has the great advantage of preserving the source's value distribution in time, but just adding a delay to it.
For more general cases:
For cold observables: switch between a never and upstream. On pause, unsubscribe from upstream. skip ones that you've seen, and then switch to the skiped stream. While unpaused you must maintain a count of how many values you've emitted, so that you can skip that many the next time someone unpauses. You only have to remember how many you've seen before. However, every unpause causes the cold observable to replay from the beginning. This could be very inefficient in the general case. The code would look something like this. pauser here would be a Subject that you can set to true or false to pause upstream.
function pausableCold(pauser, upstream) {
var seen = 0;
return pauser.switch(paused => {
if (paused) {
return Observable.never();
}
else {
return upstream.skip(seen).do(() => seen++);
}
});
}
For hot or cold observables, you can use buffering. buffer while paused and then drain and concat to the hot upstream when unpaused. (This preserves all the values but but it doesn't preserve their distribution in time. Also, you should hot upstream with publish if it could be cold.)
The most efficient way is not really part of the Rx. What you really want is to tell the source to stop emitting and then start again. The way you do that is very specific to what the source is and how the source is generating values.
I'm somewhat familiar with basic RxJS concepts like Observables, Observers and Subjects but RxJS Notifications concept is completely new to me.
What is it for? When should I use it?
The documentation you quoted mentions :
This class is particularly useful for operators that manage notifications, like materialize, dematerialize, observeOn, and others. Besides wrapping the actual delivered value, it also annotates it with metadata of, for instance, what type of push message it is (next, error, or complete).
So the question turns out to be about use cases for materialize and the like.
Basically, you use materialize to get meta-information about the dataflow without incurring into the associated side-effects (an error incurring in a stream for example propagates, a stream which completes can lead to the completion of other streams etc.). dematerialize allows to restore the side-effects.
Here are uses case from former SO questions :
Receiving done notifications from observables built using switch
RxJs - parse file, group lines by topics, but I miss the end
A use case: as errors or completions are propagated immediately, you can't for example delay them. To do so, you can try this approach:
// sample stream
interval(500).pipe(
mapTo('normal value'),
// sometimes value, sometimes throw
map(v => {
if (randomInt() > 50) {
throw new Error('boom!')
} else return v;
}),
materialize(),
// turns Observable<T> into Notification<Observable<T>>
// so we can delay or what you want
delay(500),
// and we need to do some magic and change Notification of error into
// Notification of value (error message)
map(n => n.hasValue? n : new Notification('N', n.error.message, null)),
// back to normal
dematerialize()
)
// now it never throw so in console we will have
// `normal value` or `boom!` but all as... normal values (next() emmision)
// and delay() works as expected
.subscribe(v => console.log(v))