Chained Observable after takeWhile completes is not being called? - rxjs

I have the following methods which should be called like this:
registerDomain should be called and should return an operationId
After 10 seconds, getOperationDetail should be called passing in the operationId
getOperationDetail should be called every 10 seconds until successful is returned.
Once getOperationDetail finishes, createRecordSets should be called.
Finally, getChangeStatus should be called until it returns INSYNC
If any of the api calls throw an exception, how can I handle the error on the client side?
The following code below calls registerDomain and getOperationDetail, but after getOperationDetail completes, it does not move onto createRecordSets.
registerDomain(domain) {
return this._adminService.registerDomain(domain)
.concatMap(operation => this.getOperationDetail(operation.OperationId))
.concatMap(() => this._adminService.createRecordSets(domain));
}
getOperationDetail(operationId) {
return Observable.interval(10000)
.mergeMap(() => this._adminService.getOperationDetail(operationId))
.takeWhile((info) => info.Status.Value !== 'SUCCESSFUL');
}
createRecordSets(caseWebsiteUrl) {
return this._adminService.createRecordSets(caseWebsiteUrl.Url)
.concatMap(registerId => this.getChangeStatus(registerId));
}
getChangeStatus(registerId) {
return Observable.interval(5000)
.mergeMap(() => this._adminService.getChange(registerId))
.takeWhile((info) => info.ChangeInfo.Status.Value !== 'INSYNC');
}
I updated getOperationDetail to use the first operator:
getOperationDetail(operationId) {
return Observable.interval(3000)
.mergeMap(() => this._adminService.getOperationDetail(operationId))
.first((info) => info.Status.Value === 'SUCCESSFUL')
}
Now it does in fact call createRecordSets, however, after createRecordSets, it continues to call getOperationDetail about 13 times and eventually calls getChangeStatus. The way I was looking at it, I thought it would:
Call getOperationDetail until it receives a SUCCESS.
Call createRecordSets one time.
Call getChangeStatus until it receives an INSYNC
Done.
Why the additional calls?
I changed the registerDomain to look like this:
registerDomain(domain) {
return this._adminService.registerDomain(domain)
.concatMap(operation => this.getOperationDetail(operation.OperationId))
.concatMap((op) => this.createRecordSets(op));
Before I had the .concatMap((op) => this.createRecordSets(op)) chained right after this.getOperationDetail. Once I moved it outside that, it started working as expected. I am unsure why though. Can someone explain?

When takeWhile meets a value that satisfies a specified criteria, it completes the observable without propagating the value. It means that the next chained operator will not receive the value and will not invoke its callback.
Suppose that in your example the first two calls to this._adminService.getOperationDetail(...) result in a non-successful status and the third call succeeds. It means that an observable returned by getOperationDetail() would produce only two info values each of which having non-successful status. And what might be also important, the next chained concatMap operator would invoke its callback per each of those non-successful values, meaning that createRecordSets() would be called twice. I suppose that you might want to avoid that.
I would suggest to use first operator instead:
getOperationDetail(operationId) {
return Observable.interval(10000)
.concatMap(() => this._adminService.getOperationDetail(operationId))
.first(info => info.Status.Value !== 'SUCCESSFUL');
}
This way getOperationDetail() would produce only a single "successful" value as soon as this._adminService.getOperationDetail(operationId) succeeds. The first operator emits the first value of the source observable that matches the specified condition and then completes.
And when it comes to error handling, catch or retry operators might be useful.
Update:
The unexpected behavior you have faced (getOperationDetail() keeps being called after first() completes) seems to be a bug in rxjs. As described in this issue,
every take-ish operator (one that completes earlier than its source Observable), will keep subscribing to source Observable when combined with operator that prolongs subscription (here switchMap).
Both first and takeWhile are examples of such take-ish operators and operators that "prolong" subscription are, for example, switchMap, concatMap and mergeMap. In the following example numbers will be kept logging while inner observable of concatMap is emitting values:
var takeish$ = Rx.Observable.interval(200)
// will keep logging until inner observable of `concatMap` is completed
.do(x => console.log(x))
.takeWhile(x => x < 2);
var source = takeish$
.concatMap(x => Rx.Observable.interval(200).take(10))
.subscribe();
It looks like this can be worked around by turning an observable containing such a take-ish operator into a higher-order observable — in a similar way as you've done:
var takeish$ = Rx.Observable.interval(200)
// will log only 0, 1, 2
.do(x => console.log(x))
.takeWhile(x => x < 2);
var source = Rx.Observable.of(null)
.switchMap(() => takeish$)
.concatMap(x => Rx.Observable.interval(200).take(1))
.subscribe();
Update 2:
It seems that the bug described above still exists as of rxjs version 5.4.2. It affects, for example, whether or not the source observable of the first operator will be unsubscribed when first meets the specified condition. When first operator is immediately followed by concatMap, its source observable will not be unsubscribed and will keep emitting values until inner observable of concatMap completes. In your case it means that this._adminService.getOperationDetail() would keep being called until observable returned by createRecordSets() would have completed.
Here's your example simplified to illustrate the behavior:
function registerDomain() {
return Rx.Observable.of("operation")
.concatMap(() => getOperationDetail()
.concatMap(() => Rx.Observable.interval(200).take(5)));
}
function getOperationDetail() {
return Rx.Observable.interval(100)
// console.log() instead of the actual service call
.do(x => console.log(x))
.first(x => x === 2);
}
registerDomain().subscribe();
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
If we expand the inner observable of the first concatMap operator, we will get the following observable:
Rx.Observable.interval(100)
.do(x => console.log(x))
.first(x => x === 2)
.concatMap(() => Rx.Observable.interval(200).take(5));
Notice that first is immediately followed by concatMap which prevents the source observable of the first operator (i.e. interval(100).do(x => console.log(x)) from being unsubscribed. Values will keep being logged (or in your case, service calls will keep being sent) until the inner observable of concatMap (i.e. interval(200).take(5)) completes.
If we modify the example above and move the second concatMap out of the inner observable of the first concatMap, first will not be chained with it any more and will unsubscribe from the source observable as soon as the condition is satisfied, meaning that interval will stop emitting values and no more numbers will be logged (or no more service requests will be sent):
function registerDomain() {
return Rx.Observable.of("operation")
.concatMap(() => getOperationDetail())
.concatMap(() => Rx.Observable.interval(200).take(5));
}
function getOperationDetail() {
return Rx.Observable.interval(100)
// console.log() instead of the actual service call
.do(x => console.log(x))
.first(x => x === 2);
}
registerDomain().subscribe();
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
Inner observable in such a case can be expanded simply to:
Rx.Observable.interval(100)
.do(x => console.log(x))
.first(x => x === 2)
Notice that first is not anymore followed by concatMap.
It is also worth mentioning that in both cases observable returned by registerDomain() produces exactly the same values and if we move logging from do() operator to subscribe(), the same values will be written to the console in both cases:
registerDomain.subscribe(x => console.log(x));

Related

How should I emit a single value when observable completes?

I want to emit one value when the original observable completes let's say like below, using the imaginary operator mapComplete:
let arr = ['a','b', 'c'];
from(arr)
.pipe(mapComplete(()=>'myValue'))
.pipe(map((v)=>`further processed: ${v}`))
.subscribe(console.log)
//further processed: myValue
I tried the following which work but don't seem suitable:
1.
from(arr)
.pipe(toArray())
.pipe(map(()=>'myValue'))
.pipe(map((v)=>`further processed: ${v}`))
.subscribe(console.log);
//further processed: myValue
Issue: If the original observable is a huge stream, i don't want to buffer it to an array, just to emit one value.
2.
from(arr)
.pipe(last())
.pipe(map(()=>'myValue'))
.pipe(map((v)=>`further processed: ${v}`))
.subscribe(console.log);
//further processed: myValue
issue: If the stream completes without emitting anything I get an error: [Error [EmptyError]: no elements in sequence]
What would be a correct (in rxjs terms) way to do the above?
You can achieve this with ignoreElements to not emit anything and endWith to emit a value on complete.
from(arr).pipe(
ignoreElements(),
endWith('myValue'),
map(v => `further processed: ${v}`)
).subscribe(console.log);
If you want to execute a function in map you could use count() beforehand to emit one value on complete (the amount of values emitted).
from(arr).pipe(
count(), // could also use "reduce(() => null, 0)" or "last(null, 0)" or "takeLast(1), defaultIfEmpty(0)"
map(() => getMyValue()),
map(v => `further processed: ${v}`)
).subscribe(console.log);
You may achieve what you want by building your own custom operator.
The code could look like this
const emitWhenComplete = <T>(val: T) => <U>(source: Observable<U>) =>
new Observable<T>((observer) => {
return source.subscribe({
error: (err) => observer.error(err),
complete: () => {
observer.next(val);
observer.complete();
},
});
});
Basically this operator would take in the source observable, ignore all values it emits, and emit only when the source completes.
You can look at this stackblitz for some tests.
You could also use the last() operator with a default value. It would remove the no elements in sequence error when the stream is empty.
from(arr).pipe(
last(null, 'myValue'), // `null` denotes no predicate
map(_ => 'myValue'), // map the last value from the stream
map((v)=>`further processed: ${v}`)
).subscribe(console.log);

RxJs: Observable from array of promises

I am very new to RxJs and the problem I faced looks quite complicated for me. So, I have the following sample code
const requests = [...Array(10)].map((_, i) => fetch(`${ ENDPOINT }/${ ++i }`));
from(requests).pipe(
switchMap(response => response.json()),
catchError(val => of(val))
).subscribe(value => { ... })
I have an array of 10 requests which I would like to process somehow(as an array of resolved Promise values) in my subscribe handler. The example above works just fine when I pass in only one request instead of array, but when it comes to the array I receive
TypeError: response.json is not a function
You can use forkJoin. The important thing here is that in RxJS Promises are always turned into Observables that emit once and then complete. So you don't even need to make any conversion.
const requests = [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3),
];
forkJoin(requests).subscribe(console.log);
forkJoin is typically used with an array of Observables but it works with Promises as well with zero effort.
Live demo: https://stackblitz.com/edit/rxjs-gcorux
from accepts only 1 promise. You can solve it like this:
from(Promise.all(requests))
And use map instead of switchMap. In switchmap you should return another observable, not a value.
from(Promise.all(requests)).pipe(
map(responses => responses.map(response => response.json())),
catchError(val => of(val))
).subscribe(value => { ... })
And don't forget that processing an array of promises will return array of values, so you should not just get a response.json(), but do it for each element in the array
When from takes a promise as an argument it just converts it to an observable.
You can return an array of observables instead:
const requests = [...Array(10)].map((_, i) => from(fetch(`${ENDPOINT}/${++i}`)));
And then get their value by combining the streams, perhaps with forkJoin:
forkJoin(requests).subscribe(results => console.log(results));

RxJS iif arguments are called when shouldn't

I want to conditionally dispatch some actions using iif utility from RxJS. The problem is that second argument to iif is called even if test function returns false. This throws an error and app crashes immediately. I am new to to the power of RxJS so i probably don't know something. And i am using connected-react-router package if that matters.
export const roomRouteEpic: Epic = (action$, state$) =>
action$.ofType(LOCATION_CHANGE).pipe(
pluck('payload'),
mergeMap(payload =>
iif(
() => {
console.log('NOT LOGGED');
return /^\/room\/\d+$/.test(payload.location.pathname); // set as '/login'
},
merge(
tap(v => console.log('NOT LOGGED TOO')),
of(
// following state value is immediately evaluated
state$.value.rooms.list[payload.location.pathname.split('/')[1]]
? actions.rooms.initRoomEnter()
: actions.rooms.initRoomCreate(),
),
of(actions.global.setIsLoading(true)),
),
empty(),
),
),
);
A little late to the party, but I found that the role of iif is not to execute one path over the other, but to subscribe to one Observable or the other. That said, it will execute any and all code paths required to get each Observable.
From this example...
import { iif, of, pipe } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
const source$ = of('Hello');
const obsOne$ = (x) => {console.log(`${x} World`); return of('One')};
const obsTwo$ = (x) => {console.log(`${x}, Goodbye`); return of('Two')};
source$.pipe(
mergeMap(v =>
iif(
() => v === 'Hello',
obsOne$(v),
obsTwo$(v)
))
).subscribe(console.log);
you'll get the following output
Hello World
Hello, Goodbye
One
This is because, in order to get obsOne$ it needed to print Hello World. The same is true for obsTwo$ (except that path prints Hello, Goodbye).
However you'll notice that it only prints One and not Two. This is because iif evaluated to true, thus subscribing to obsOne$.
While your ternary works - I found this article explains a more RxJS driven way of achieving your desired outcome quite nicely: https://rangle.io/blog/rxjs-where-is-the-if-else-operator/
Ok, i found an answer on my own. My solution is to remove iif completely and rely on just ternary operator inside mergeMap. that way its not evaluated after every 'LOCATION_CHANGE' and just if regExp returns true. Thanks for your interest.
export const roomRouteEpic: Epic = (action$, state$) =>
action$.ofType(LOCATION_CHANGE).pipe(
pluck<any, any>('payload'),
mergeMap(payload =>
/^\/room\/\d+$/.test(payload.location.pathname)
? of(
state$.value.rooms.list[payload.location.pathname.split('/')[2]]
? actions.rooms.initRoomEnter()
: actions.rooms.initRoomCreate(),
actions.global.setIsLoading(true),
)
: EMPTY,
),
);
If you use tap operator inside observable creation(because it returns void), it will cause error as below
Error: You provided 'function tapOperatorFunction(source) {
return source.lift(new DoOperator(nextOrObserver, error, complete));
}' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
Remove the tap and put the console in the subscribe().
I have created a stackblitz demo.
Another consideration is that even though Observables and Promises can be used in the same context many times when working with RxJS, their behavior will be different when dealing with iif. As mentioned above, iif conditionally subscribes; it doesn't conditionally execute. I had something like this:
.pipe(
mergeMap((input) =>
iif(() => condition,
functionReturningAPromise(input), // A Promise!
of(null)
)
)
)
This was evaluating the Promise-returning function regardless of the condition because Promises don't need to be subscribed to to run. I fixed it by switching to an if statement (a ternary would have worked as well).

rxjs - what is the difference from result perspective to do actions in .do() and .subscribe()

For examples, I have the following code that I can write in two ways:
1.
ObsevableA.do(() => {
doSomething();
})
.subscribe();
2.
ObservableA.subscribe(() => {
doSomething();
});
The end result seems to be the same. Any thoughts on each approach? What is the best practice I should be using? Thanks.
I'd summarize it into a few points:
The Observable chain doesn't emit anything until you call subscribe().
You can call subscribe() with no parameters while do() always needs to have at least one parameter (because there's no point in using do() without any handler).
If your chain sends an error and you don't setup any error handler in you subscribe() call the error will be rethrow. This doesn't apply to .do() because it just passes everything through without any further logic.
In general it's recommended to use do() only to perform side-effects and to debug your Observable chains. The good thing with do() is that you can place it anywhere you want but you should avoid using it to modify the chain values further.
For example you shouldn't do something like this:
let sum = 0;
Observable.range(1, 3)
.do(val => sum += val)
.concat(Observable.of({ total: sum}))
.subscribe(console.log);
This prints:
1
2
3
{total: 6}
When you can write it in more Rx way:
Observable.range(1, 3)
.publish(shared => Observable
.merge(shared, shared.reduce((acc, x) => acc + x, 0).map(sum => { return { total: sum }; }))
)
.subscribe(console.log);
do() returns an output observable and doesn't consume values from the source observable. You can use it for side effects in the reactive chain.
subscribe() returns a subscription and does consume values from the source observable. You use it at the end of the reactive chain to process the result of your computation. The returned subscription object has a dispose() method you can call to unsubscribe all the observables.

How to force observables to execute in sequence?

I am moving from the Promise world to the Observable world. One thing I usually do with Promise is to chain a series of tasks and make them run in sequence. For example, I have three tasks: printLog1() to print 1 to the console, printLog23() to print 2 and 3 to the console, and printLog4() to print 4.
When I want to print 1-2-3-4, I would write a promise chain like
printLog1()
.then(() => {
printLog23();
})
.then(() => {
printLog4();
});
Now I want the same functionality with Observable and I can rewrite the printLog() function into an Observable like
printLog1 = Rx.Observabale.of(1).map((i) => console.log(i));
printLog23 = Rx.Observabale.of(2, 3).map((i) => console.log(i));
printLog4 = Rx.Observabale.of(4).map((i) => console.log(i));
Then I have three observables that emits different values to the console. How do I chain them so that these three observables would run in order and print 1-2-3-4?
If you want to be sure the order of emissions is the same as the order in which you specified the source Observables you can use concat or concatMap operators.
The concat* operators subscribe to an Observable only after the previous Observable completes (it works with Promises as well, see http://reactivex.io/rxjs/class/es6/MiscJSDoc.js~ObservableInputDoc.html).
In you case it'd look like the following:
import { concat } from 'rxjs'; // Note, concat from 'rxjs', is not the same as concat from 'rxjs/operators'
concat(printLog1, printLog23, printLog4);
... or with concatMap if the request for one Promise depends on the response from the previous Promise:
printLog1.pipe(
concatMap(response => ...),
concatMap(response => ...),
);
... or when the order doesn't matter you can use merge that subscribes to all Observables/Promises immediately and reemits their results as they arrive:
merge(printLog1, printLog23, printLog4);
Jan 2019: Updated for RxJS 6
My solution:
const sequence: Observable<any>[] = [of(1), of(2), of(3)];
return concat(...sequence).pipe(toArray());

Resources