Suppose I want observable to emit value periodically until another observable emits. So I can use timer and takeUntil to achive that.
But then I want to process every emitted value and stop (error) emitting when some condition becomes true. So I write next piece of code:
const { timer, Subject } = 'rxjs';
const { takeUntil, map } = 'rxjs/operators';
const s = new Subject();
let count = 0;
function processItem(item) {
count++;
return count < 3;
}
const value = "MyValue";
timer(0, 1000).pipe(
takeUntil(s),
map(() => {
if (processItem(value)) {
console.log(`Processing done`);
return value;
} else {
s.next(true);
s.complete();
console.error(`Processing error`);
throw new Error(`Stop pipe`);
}
}),
)
Playground
But instead of getting error I have my Observable completed.
Only if I comment out takeUntil(s) operator, I get error.
Looks like when pipe operator completes, it's value is not emitted immediately, but remembered and emitted at the end of next "iteration" of the pipe, then replaced by new result and so on. And in my situation next iteration, when error should be emitted, is prevented by takeUntil. Question is am I right with that assumption, and if so, why rxjs is designed in that way?
First of all, each Rx chain can emit one error or one complete notification but never both. (See http://reactivex.io/documentation/contract.html section "The Contract Governing Notifications").
takeUntil operator emits complete when it's notification Observable (s in your case) emits any next notification. This means that when s emits the chain will be completed and you never receive any further error notifications.
The last thing and probably most confusing is that everything in RxJS happens synchronously unless you work with time (eg. delay operator) or you specifically use observeOn operator with an asynchronous scheduler. So when you call s.next(true) inside map this next notification is immediately propagated to takeUntil which completes the chain and as I mentioned above, you can receive one error or one complete notification but never both.
It looks like you don't even need to be using takeUntil because if you throw an error inside map it's automatically wrapped and sent further as an error notification (How to throw error from RxJS map operator (angular)) and the chain is disposed automatically so there's no point in trying to complete it after that with takeUntil.
Related
I am new to Angular (just learning) and I am trying to understand the following code. In this code (I simplified it from another app I have seen on Github) subscribe method calls add method with the parameter resolve (from Promise) - so I have a few questions:
Doesn't parameter passed to add mehtod have to be of type `Subscription' ?
What does framework do with passed resolve parameter. I Thought the parameter must be of type Subscription and framework calls <Subscription>.unsubscribe() on it.
const numbers: Observable<number> = interval(5000);
const takeFourNumbers = numbers.pipe(take(4));
const promise$ = new Promise<void>(resolve => {
// attempt to refresh token on app start up to auto authenticate
takeFourNumbers.subscribe()
.add(resolve);
}).then((result) => {
console.log("My result is ", result);
});
That's a bit of an odd piece of code.
Every Subscription has an .add( function, which is used to trigger something else on teardown, this is when that subscription ends (either because the stream errors, completes, or you call .unsubscribe())
The parameter that .add( takes can be a function (in which case it just gets called on teardown), or another subscription, in which case it will call .unsubscribe() to it.
It's not really used too much unless you're building something low-level (such a library)
In this case, takeFourNumbers is a stream that will emit 4 numbers in succession, 1 second between each emission and then complete. promise$ is a Promise that when takeForNumbers ends after 4 seconds it will resolve with void, because the teardown will call the function passed to .add(, which is resolve, and it doesn't give any parameter as far as I know.
Then on that promise it calls .then( to log the result when that happens, but I expect the result to be undefined.
I am working with a TypeScript, Angular, NGRX application. I have been writing my state observables without using selectors - the main reason is that I have found that they are less powerful than using RxJS operators directly. As an example, it is not possible to restrict the emission of events using selectors alone - instead a filtering operator must be used.
For the most part, I have had no issues replacing selectors with observables - observables can compose in all of the same ways that selectors can - with one exception: I cannot figure out how to compose observables which may be triggered from the same action. Usually, I have used combineLatest as my goto observable composer; however, in the case when two observables would update on the same action, there is a transient update where one of the observables has a value from the new state and the other has a value from the previous state.
Originally, I considered using the zip observable creator instead; however, while this solves the problem when two observables update together, it does not solve the problem when one observable is updated without the other - as is entirely possible with an NGRX architecture.
I then considered the auditTime(0) operator, which does solve the problem of removing the transient update, but has new problems
1) It causes observables to emit on a later event loop which breaks some assumptions inside of the application (solvable, but annoying)
2) It causes various observables to emit as soon as they can, whereas I would like all observables to emit together, on the same store pulse. Graphically, this means that rendering of different parts of the application are staggered, instead of being drawn together on the same frame (note that our application is very data-heavy, and it is often necessary to drop frames on store pulses)
Finally, I wrote a custom operator to compose observables which are derived from the same source
export type ObservableTuple<TupleT extends any[]> = {
[K in keyof TupleT]: Observable<TupleT[K]>;
};
export function selectFrom<SourceT, TupleT extends any[]>(...inputs: ObservableTuple<TupleT>): OperatorFunction<SourceT, TupleT> {
return (source$: Observable<SourceT>) => source$.pipe(
withLatestFrom(combineLatest<TupleT>(inputs)),
map(([, values]) => values),
);
}
Here is a summary of the problem in TypeScript (using snippets of NGRX, RxJS, and Angular)
interface IState {
foo: string;
bar: string;
}
#Injectable()
class SomeService {
constructor(store$: Store<IState>) {
}
readonly foo$ = this.store$.pipe(select(state => state.foo));
readonly bar$ = this.store$.pipe(select(state => state.bar));
readonly composed$ = this.store$.pipe(
selectFrom(
this.foo$,
this.bar$,
),
map(([foo, bar]) => `${foo} - ${bar}`),
);
}
const UPDATE_FOO = {
type: 'update foo',
foo: 'some updated value for foo'
};
const UPDATE_BAR = {
type: 'update bar',
bar: 'some updated value for bar',
};
const UPDATE_BOTH = {
type: 'update both',
both: 'some updated value for both foo and bar',
};
This works perfectly correctly even when selectFrom calls are nested within one another e.g.
readonly composed2$ = this.store$.pipe(
selectFrom(
this.composed$,
this.foo$
)
)
So long as composed$ is defined before composed2$, everything works out; however, a case I did not consider is when using an operator like switchMap in between composed$ and composed2$. In this case, because compsed2$ is destroyed and recreated by switchMap, it is possible for composed2$ to fire before composed$, which causes everything to get out of sync
For your specific problem of trying to compose 2 observables and only emit after both of them have finished emitting, you can try to take advantage of:
queue Scheduler - lets you defer recursive calls until the current call completes
debounce - delay an update until a signal arrives
observeOn - only listen to store updates on the queue Scheduler
Then you could do something like the following:
readonly queuedStore$ = this.store$.pipe(
observeOn(queue), // use queue scheduler to listen to store updates
share() // use the same store event to update all of our selectors
);
// use queuedStore$ here
readonly foo$ = this.queuedStore$.pipe(select(state => state.foo));
readonly bar$ = this.queuedStore$.pipe(select(state => state.bar));
// when composing, debounce the combineLatest() with an observable
// that completes immediately, but completes on the queue scheduler
readonly composed$ = combineLatest(foo$, bar$).pipe(
debounce(() => empty().pipe(observeOn(queue))));
What will happen?
Foo Update
queuedStore$ schedules notification on queue
notification starts immediately since nothing is currently running
foo$ notifies
combineLatest notifies
debounce subscribes to durationSelector
durationSelector schedules notification on queue
notification is not sent, since queued action is currently running
call stack unwinds to step 1
queue scheduler runs durationSelector notification
debounce triggers and sends out update to UI
Bar Update
Works same as Foo update
BarFoo Update
queuedStore$ schedules notification on queue
notification starts immediately since nothing is currently running
foo$ notifies
combineLatest notifies
debounce subscribes to durationSelector
durationSelector schedules notification on queue
notification is not sent, since queued action is currently running
call stack unwinds to step 3
bar$ notifies
combineLatest notifies
debounce throws away previous value from foo notification
debounce resubscribes to durationSelector
durationSelector schedules notification on queue
notification is not sent, since queued action is currently running
call stack unwinds to step 1
queue scheduler runs durationSelector notification
debounce triggers and sends out update to UI
In theory this gets you your desired behavior:
- Single updates apply immediately (before next tick)
- Combined update applies immediately (before next tick)
- Combined updates ignore intermediate result
- Should still work if your composed observable uses switch.
Thing to watch out for
If you dispatch another event while handling one of these notifications on the queue scheduler, the notifications for that 2nd event will be deferred until after the current handler completes.
Angular 7 docs provide this example of practical usage of rxjs Observables in implementing an exponential backoff for an AJAX request:
import { pipe, range, timer, zip } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { retryWhen, map, mergeMap } from 'rxjs/operators';
function backoff(maxTries, ms) {
return pipe(
retryWhen(attempts => range(1, maxTries)
.pipe(
zip(attempts, (i) => i),
map(i => i * i),
mergeMap(i => timer(i * ms))
)
)
);
}
ajax('/api/endpoint')
.pipe(backoff(3, 250))
.subscribe(data => handleData(data));
function handleData(data) {
// ...
}
While I understand the concept of both Observables and backoff, I can’t quite figure out, how exactly retryWhen will calculate time intervals for resubscribing to the source ajax.
Specifically, how do zip, map, and mapMerge work in this setup?
And what’s going to be contained in the attempts object when it’s emitted into retryWhen?
I went through their reference pages, but still can’t wrap my head around this.
I have spent quite some time researching this (for learning purposes) and will try to explain the workings of this code as thoroughly as possible.
First, here’s the original code, annotated:
import { pipe, range, timer, zip } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { retryWhen, map, mergeMap } from 'rxjs/operators';
function backoff(maxTries, ms) { // (1)
return pipe( // (2)
retryWhen(attempts => range(1, maxTries) // (3)
.pipe(
zip(attempts, (i) => i), // (4)
map(i => i * i), // (5)
mergeMap(i => timer(i * ms)) // (6)
)
)
); // (7)
}
ajax('/api/endpoint')
.pipe(backoff(3, 250))
.subscribe(data => handleData(data));
function handleData(data) {
// ...
}
Easy enough, we’re creating custom backoff operator out of retryWhen operator. We’ll be able to apply this later within pipe function.
In this context, pipe method returns a custom operator.
Our custom operator is going to be a modified retryWhen operator. It takes a function argument. This function is going to be called once — specifically, when this retryWhen is first encountered/invoked. By the way, retryWhen gets into play only when the source observable produces an error. It then prevents error from propagating further and resubscribes to the source. If the source produces a non-error result (whether on first subscription or on a retry), retryWhen is passed over and is not involved.
A few words on attempts. It’s an observable. It is not the source observable. It is created specifically for retryWhen. It has one use and one use only: whenever subscription (or re-subscription) to the source observable results in an error, attempts fires a next. We are given attempts and are free to use it in order to react in some way to each failed subscription attempt to the source observable.
So that’s what we are going to do.
First we create range(1, maxTries), an observable that has an integer for every retry we are willing to perform. range is ready to fire all it’s numbers right then and there, but we have to hold its horses: we only need a new number when another retry happens. So, that’s why we...
... zip it with the attempts. Meaning, marry each emitted value of attempts with a single value of range.
Remember, function we’re currently in is going to be called only once, and at that time, attempts will have only fired next once — for the initial failed subscription. So, at this point, our two zipped observables have produced just one value.
Btw, what are the values of the two observables zipped into one? This function decides that: (i) => i. For clarity it can be written (itemFromRange, itemFromAttempts) => itemFromRange. Second argument is not used, so it’s dropped, and first is renamed into i.
What happens here, is we simply disregard the values fired by attempts, we are only interested in the fact that they are fired. And whenever that happens we pull the next value from range observable...
...and square it. This is for the exponential part of the exponential backoff.
So, now whenever (re-)subscription to source fails, we have an ever increasing integer on our hands (1, 4, 9, 16...). How do we transform that integer into a time delay until next re-subscription?
Remember, this function we are currently inside of, it must return an observable, using attempts as input. This resulting observable is only built once. retryWhen then subscribes to that resulting observable and: retries subscribing to source observable whenever resulting observable fires next; calls complete or error on source observable whenever resulting observable fires those corresponding events.
Long story short, we need to make retryWhen wait a bit. delay operator could maybe be used, but setting up exponential growth of the delay would likely be pain. Instead, mergeMap operator comes into play.
mergeMap is a shortcut for two operators combined: map and mergeAll. map simply converts every increasing integer (1, 4, 9, 16...) into a timer observable which fires next after passed number of milliseconds. mergeAll forces retryWhen to actually subscribe to timer. If that last bit didn’t happen, our resulting observable would just fire next immediately with timer observable instance as value.
At this point, we’ve built our custom observable which will be used by retryWhen to decide when exactly to attempt to re-subscribe to source observable.
As it stands I see two problems with this implementation:
As soon as our resulting observable fires its last next (causing the last attempt to resubscribe), it also immediately fires complete. Unless the source observable returns result very quickly (assuming that the very last retry will be the one that succeeds), that result is going to be ignored.
This is because as soon as retryWhen hears complete from our observable, it calls complete on source, which may still be in the process of making AJAX request.
If all retries were unsuccessful, source actually calls complete instead of more logical error.
To solve both these issues, I think that our resulting observable should fire error at the very end, after giving the last retry some reasonable time to attempt to do its job.
Here’s my implementation of said fix, which also takes into account deprecation of zip operator in latest rxjs v6:
import { delay, dematerialize, map, materialize, retryWhen, switchMap } from "rxjs/operators";
import { concat, pipe, range, throwError, timer, zip } from "rxjs";
function backoffImproved(maxTries, ms) {
return pipe(
retryWhen(attempts => {
const observableForRetries =
zip(range(1, maxTries), attempts)
.pipe(
map(([elemFromRange, elemFromAttempts]) => elemFromRange),
map(i => i * i),
switchMap(i => timer(i * ms))
);
const observableForFailure =
throwError(new Error('Could not complete AJAX request'))
.pipe(
materialize(),
delay(1000),
dematerialize()
);
return concat(observableForRetries, observableForFailure);
})
);
}
I tested this code and it seems to work properly in all cases. I can’t be bothered to explain it in detail right now; I doubt anyone will even read the wall of text above.
Anyway, big thanks to #BenjaminGruenbaum and #cartant for setting me onto right path for wrapping my head around all this.
Here is a different version that can be easily extended/modified:
import { Observable, pipe, throwError, timer } from 'rxjs';
import { mergeMap, retryWhen } from 'rxjs/operators';
export function backoff(maxRetries = 5): (_: Observable<any>) => Observable<any> {
return pipe(
retryWhen(errors => errors.pipe(
mergeMap((error, i) => {
const retryAttempt = i + 1;
if (retryAttempt > maxRetries) {
return throwError(error);
} else {
const waitms = retryAttempt * retryAttempt * 1000;
console.log(`Attempt ${retryAttempt}: retrying in ${waitms}ms`);
return timer(waitms);
}
}),
))
);
};
Ref retryWhen
I have three observable sources in my code that emit values of the same type.
const setTitle$ = params$.do(
params => this.titleService.setTitle( `${params[1].appname} - ${this.pagename}` )
).switchMap(
() => Observable.of(true)
);
const openDocument$ = params$.switchMap(
params => this.openDocument(params[0].id)
);
const saveDocument$ = params$.switchMap(
params => this.saveDocument(params[0].id)
);
When i use them in race like this
setTitle$.race(
openDocument$,
saveDocument$
).subscribe();
works only setTitle and when i subscribe manually to another two sorces like
const openDocument$ = params$.switchMap(
params => this.openDocument(params[0].id)
).subscribe();
const saveDocument$ = params$.switchMap(
params => this.saveDocument(params[0].id)
).subscribe();
then they work too. Help me understand why it's going on and how to force to work all sources in race, merge, etc.
From the documentation, the .race() operator does this:
The observable to emit first is used.
That is why, you will only get ONE emission, because only one out of the three observables that emits first will get emitted.
What you are looking for is .forkJoin() or .combineLatest().
If you want all the observables to execute in parallel and wait for ALL of them to come back as one observables, use .forkJoin():
Observable
.forkJoin([...setTitle$, openDocument$, saveDocument$])
.subscribe(([setTitle, openDocument, saveDocument]) => {
//do something with your your results.
//all three observables must be completed. If any of it was not completed, the other 2 observables will wait for it
})
If you however wants to listen to every emission of all the observables regardless when they are emitted, use .combineLatest():
Observable
.combineLatest(setTitle$, openDocument$, saveDocument$)
.subscribe(([setTitle, openDocument, saveDocument]) => {
//do something with your your results.
// as long as any of the observables completed, it will be emitted here.
});
Problem was with shared params source.
const params$ = this.route.params.map(
routeParams => {
return {
id: <string>routeParams['id']
};
}
).combineLatest(
this.config.getConfig()
).share();
I have shared it with share operator. But in this article from the first comment to my question i found this:
When using multiple async pipes on streams with default values, the .share() operator might cause problems:
The share() will publish the first value of the stream on the first subscription. The first async pipe will trigger that subscription and get that initial value. The second async pipe however will subscribe after that value has already been emitted and therefore miss that value.
The solution for this problem is the .shareReplay(1) operator, which will keep track of the previous value of the stream. That way all the async pipes will get the last value.
I replaced share() with shareReplay(1) and all sources began emitting values.
const params$ = this.route.params.map(
routeParams => {
return {
id: <string>routeParams['id']
};
}
).combineLatest(
this.config.getConfig()
).shareReplay(1);
Thanks to everyone for help!
Let's say we have an Observable:
var observable = Rx.Observable
.fromEvent(document.getElementById('emitter'), 'click');
How can I make it Complete (what will trigger onComplete event for all subscribed Observers) ?
In this present form, you cannot. Your observable is derived from a source which does not complete so it cannot itself complete. What you can do is extend this source with a completing condition. This would work like :
var end$ = new Rx.Subject();
var observable = Rx.Observable
.fromEvent(document.getElementById('emitter'), 'click')
.takeUntil(end$);
When you want to end observable, you do end$.onNext("anything you want here");. That is in the case the ending event is generated by you. If this is another source generating that event (keypress, etc.) then you can directly put an observable derived from that source as an argument of takeUntil.
Documentation:
http://reactivex.io/documentation/operators/takeuntil.html
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/takeuntil.md
What worked for me is using the take() operator. It will fire the complete callback after x number of events. So by passing 1, it will complete after the first event.
Typescript:
private preloadImage(url: string): Observable<Event> {
let img = new Image();
let imageSource = Observable.fromEvent(img, "load");
img.src = url;
return imageSource.take(1);
}
I think what you are looking for is the dispose() method.
from: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables
Notice that the subscribe method returns a Disposable, so that you can unsubscribe to a sequence and dispose of it easily. When you invoke the dispose method on the observable sequence, the observer will stop listening to the observable for data. Normally, you do not need to explicitly call dispose unless you need to unsubscribe early, or when the source observable sequence has a longer life span than the observer. Subscriptions in Rx are designed for fire-and-forget scenarios without the usage of a finalizer. Note that the default behavior of the Observable operators is to dispose of the subscription as soon as possible (i.e, when an onCompleted or onError messages is published). For example, the code will subscribe x to both sequences a and b. If a throws an error, x will immediately be unsubscribed from b.
I found an easier way to do this for my use case, If you want to do something when the observable is complete then you can use this:
const subscription$ = interval(1000).pipe(
finalize(() => console.log("Do Something")),
).subscribe();
The finalize is triggered on complete, when all subscriptions are unsubscribed etc.