According to rxjs marbles documentation the current behaviour for the sync groupings is the following:
'(ab)-(cd)': on frame 0, emits a and b then on frame 50, emits c and d
From the docs:
While it can be unintuitive at first, after all the values have synchronously emitted time will progress a number of frames equal to the number of ASCII characters in the group, including the parentheses
Ok, but how do I test an observable like this (using marbles or any other technique):
const observable$ = of(1, 2).concat(of(3, 4).delay(20));
Are there any workarounds?
There is a similar question on Stack Overflow but there is no answer on 'How to actually work around it and test this kind of observable'.
Thanks!
For my project I migrated to rx-sanbox where sync grouping works correct and it solved my problem.
So, in rx-sandbox this is correct:
'(ab)-(cd)': on frame 0, emits a and b then on frame 20, emits c and d
I don't know what version of RxJS you're using because you're mixing prototypical and pipable operators but it looks like RxJS 5.5.
In RxJS 5.X it's a bit clumsy. You could rewrite your test like this:
import { of } from 'rxjs/observable/of';
import { TestScheduler } from 'rxjs/testing/TestScheduler';
import { assert } from 'chai';
import 'rxjs/add/operator/concat';
import 'rxjs/add/operator/delay';
const scheduler = new TestScheduler((actual, expected) => {
console.log(actual, expected);
return assert.deepEqual(actual, expected);
});
const observable$ = of('a', 'b').concat(of('c', 'd').delay(50, scheduler));
scheduler
.expectObservable(observable$)
.toBe('(ab)-(cd|)');
scheduler.flush();
See live demo (open console): https://stackblitz.com/edit/rxjs5-marble-test?file=index.ts
You know this test passes because it doesn't throw any error. Try changing any of the delays or values of next emissions and it'll throw an error.
Also have a look at this answer: How do I test a function that returns an observable using timed intervals in rxjs 5?
However, I'd strongly recommend upgrading to RxJS 6 because it makes everything much easier with cold and hot "creation" functions where you could just use const observable$ = cold('(ab)-(cd|)') to create the same sequence as you're doing with of(...).concat(...).
Testing in RxJS 6:
https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md
https://github.com/ReactiveX/rxjs/blob/master/doc/internal-marble-tests.md
Related
I have an observable that performs some relatively taxing work so I need to use the shareReplay(1) operator, it's important that I have access to an emitted value immediately as well so share won't quite achieve everything I need.
The issue is that when I'm trying to cleanup and unsubscribe the source observable will keep emitting, which I believe is caused by shareReplay keeping it alive due to refCount defaulting to false. This is also the behavior I need as at application start up the reference count will jump from 1 to 0 to 1 essentially, I don't want to observable restarting on a new subscription.
Is there any way to unsubscribe and stop the source observable emitting when using shareReplay? Seems a bit strange to have no way to clean up resources when using that particular operator.
A simple example:
const first = interval(10_000).pipe(
startWith(0),
withLatestFrom(someOtherObs),
map(([i, val]) => {
// some work
}),
shareReplay(1)
)
const second = interval(5_000).pipe(
withLatestFrom(first),
take(5)
)
const main = interval(5_000).pipe(
withLatestFrom(first),
map(([i, val]) => {
// perform work
})
)
const app = from([second, main]).pipe(concatAll()).subscribe()
Basically second will complete before main will start (hence the need for shareReplay), however it will continue emitting in perpetuity due to the interval... Is there anything I can do to avoid this while achieving the same behavior... Any help would be greatly appreciated.
In your case you'll need to force the source Observable of shareReplay(1) to complete for example with takeUntil() operator.
const done$ = new Subject();
const first = interval(10_000).pipe(
startWith(0),
withLatestFrom(someOtherObs),
map(([i, val]) => {
// some work
}),
takeUntil(done$),
shareReplay(1)
)
// ...
done$.next();
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.
TLDR: Working example is in the last codeblock of this question. Check out #bryan60 answer for a working example using concat rather than mergeMap.
I'm trying to run a number of remote requests sequentially, but only the first observable is executed.
The number of request vary, so I can't do a dodgy solution where I nest observables within each other.
I'm using the following code:
const observables = [
observable1,
observable2,
...
];
from(observables).pipe(
mergeMap(ob=> {
return ob.pipe(map(res => res));
}, undefined, 1)
).subscribe(res => {
console.log('Huzzah!');
})
In the past (rxjs 5.5) Ive used the following:
let o = Observable.from(observables).mergeMap((ob) => {
return ob;
}, null, 1);
o.subscribe(res => {
console.log('Huzzah!');
})
I'm not sure what I'm doing wrong, can anybody shed some light?
An additional request would be to only print 'Huzzah!' once on completion of all requests rather than for each individual Observable.
EDIT:
Removing undefined from my original code will make it work, however there was another issue causing only the first observable to be executed.
I'm using Angular's HttpClient for remote requests. My observable code looked like this:
const observables = [];
// Only the first observable would be executed
observables.push(this.http.get(urla));
observables.push(this.http.get(urlb));
observables.push(this.http.get(urlc));
Adding .pipe(take(1)) to each observable results in each observable being executed:
const observables = [];
// All observables will now be executed
observables.push(this.http.get(urla).pipe(take(1));
observables.push(this.http.get(urlb).pipe(take(1));
observables.push(this.http.get(urlc).pipe(take(1));
The code I ended up using, which executes all observables in sequential order and only triggers Huzzah! once is:
const observables = [];
observables.push(this.http.get(urla).pipe(take(1));
observables.push(this.http.get(urlb).pipe(take(1));
observables.push(this.http.get(urlc).pipe(take(1));
from(observables).pipe(
mergeMap(ob=> {
return ob.pipe(map(res => res));
}, 1),
reduce((all: any, res: any) => all.concat(res), [])
).subscribe(res => {
console.log('Huzzah!');
})
Thanks to #bryan60 for helping me wit this issue.
if these are http requests that complete, I think your bug is caused by a change to the mergeMap signature that removed the result selector. it's hard to be sure without knowing exactly which version you're on as it was there, then removed, then added again, and they're removing it once more for good in v7.
if you want to run them sequentially... this is all you need...
// concat runs input observables sequentially
concat(...observables).subscribe(res => console.log(res))
if you want to wait till they're all done to emit, do this:
concat(...observables).pipe(
// this will gather all responses and emit them all when they're done
reduce((all, res) => all.concat([res]), [])
// if you don't care about the responses, just use last()
).subscribe(allRes => console.log(allRes))
In my personal utility rxjs lib, I always include a concatJoin operator that combines concat and reduce like this.
the only trick is that concat requires observables to complete till it moves on to the next one, but the same is true for mergeMap with concurrent subscriptions set to 1.. so that should be fine. things like http requests are fine, as they complete naturally after one emission.. websockets or subjects or event emitters will behave a bit differently and have to be manually completed, either with operators like first or take or at the source.
If you are not concerned about the sequence of execution and just want 'Huzzah!' to be printed once all the observable has been executed forkJoin can also be used.Try this.
forkJoin(...observables).subscribe(res => console.log('Huzzah');
Starting with an empty observable I have a steady stream of non-rxjs events that I need to throttle with rxjs but i cant find a way to create a throttled output. In my use case I do not know when the first value will arrive and nor can I determine the frequency of new values arriving.
https://stackblitz.com/edit/rxjs-behaviorsubject-simpleexample-etebvz?file=index.ts
I was expecting this example to work and display the values added with next() to be throttled 1s apart but its not working.
import { BehaviorSubject, interval } from 'rxjs';
import { tap, map, throttle } from 'rxjs/operators';
const subject = new BehaviorSubject(1);
const example = subject.pipe(
throttle(ev => interval(1000)),
tap((ev) => console.log(ev))
)
example.subscribe();
example.next(2);
example.next(3);
example.next(4);
example.next(5);
example.next(6);
I cant find any online examples to match this (apparently) simple use case and working with rxjs feels unintuitive to achieve this. Any help is much appreciated.
throttleTime allows you to specify a number of milliseconds to wait before emitting again.
const example = subject.pipe(
throttleTime(1000),
tap((ev) => console.log(ev))
)
Stackblitz
I recommend checking out the operator decision tree and clicking through to see what options are available:
I have one existing Observable
I want to ignore values
that occur too frequently
I wonder how to implement this properly with RxJs (4/5)?
-a-- -b----c----d-----------------------------------------------------------e------f---------------------
-5-sec after-"a"--> [abcd]---new 5 sec timer will start when "e" emited-----5 sec-after-"e"->[ef]-
I think this:
.buffer(source$.throttleTime(5000).debounceTime(5000))
do the job in rxjs 5
Your best shot is to use buffer. The buffer has a closing condition, and you'd like a closing condition 5 seconds after a new item was introduced. So, lets suppose you have a source stream, your desired stream will be:
source.buffer(source.throttle(5100).debounce(5000));
This is rxjs 4. I think rxjs has a slightly different buffer operators but the idea is the same.
Explanation:
The throttle ensures that for 5100 mSecs you will get only the first "tick". The debounce will propagate this "tick" after 5000 mSecs because there were no other "ticks" since. Note that I chose 5100 mSecs since the timing is not always perfect and if you use 5000 mSecs for both, the debounce might be repeatedly delayed and you'll get starvation. Anyways, your buffer will not loose data, just might group it in chunks bigger than 5000 mSecs.
Rxjs 5 has a bufferToggle operator which might look a better option, yet, the fact that you both open and close the buffer might become risky and make you loose data due to timing issues.
I am using RxJS 6 and could not readily find the documentation for 5. However, this is a fantastic question. Here was my result which is also demonstrated in a real example reproducing a bug in Angular Material.
source$ = source$.pipe(buffer(source$.pipe(debounceTime(5000))));
Having tried all Rxjs 5 buffer variants, in particular bufferTime which emits every n seconds empty or not, I ended up rolling my own bufferTimeLazy:
function bufferTimeLazy(timeout) {
return Rx.Observable.create(subscriber => {
let buffer = [], hdl;
return this.subscribe(res => {
buffer.push(res);
if (hdl) return;
hdl = setTimeout(() => {
subscriber.next(buffer);
buffer = [];
hdl = null;
}, timeout);
}, err => subscriber.error(err), () => subscriber.complete());
});
};
// add operator
Rx.Observable.prototype.bufferTimeLazy = bufferTimeLazy;
// example
const click$ = Rx.Observable.fromEvent(document, 'click');
click$.bufferTimeLazy(5000).subscribe(events => {
console.log(`received ${events.length} events`);
});
Example:
https://jsbin.com/nizidat/6/edit?js,console,output
The idea is to collect events in a buffer and emit the buffer n seconds after first event. Once emitted, empty buffer and remain dormant until next event arrives.
If you prefer not to add operator to Observable.prototype, just invoke the function:
bufferTimeLazy.bind(source$)(5000)
EDIT:
Ok, so it's not all bad with Rxjs 5:
var clicks = Rx.Observable.fromEvent(document, 'click').share();
var buffered = clicks.bufferWhen(() => clicks.delay(5000));
buffered.subscribe(x => console.log(`got ${x.length} events`));
Achieves the same. Notice share() to avoid duplicate click subscriptions - YMMV.
As Trevor mentioned, in RXJS 6 there is no official way but clearly you need to use debounce + buffer in order to achieve that result.
To make things properly, in Typescript and with Type Inference, I created a custom OperatorFunction called bufferDebounce that makes a lot easier to use and understand this operator.
The snippet with type inference
type BufferDebounce = <T>(debounce: number) => OperatorFunction<T, T[]>;
const bufferDebounce: BufferDebounce = debounce => source =>
new Observable(observer =>
source.pipe(buffer(source.pipe(debounceTime(debounce)))).subscribe({
next(x) {
observer.next(x);
},
error(err) {
observer.error(err);
},
complete() {
observer.complete();
},
})
// [as many sources until no emit during 500ms]
source.pipe(bufferDebounce(500)).subscribe(console.log)
You can try it in this working example: https://stackblitz.com/edit/rxjs6-buffer-debounce