What's more appropriate - concat or map? - rxjs

I want to execute 2 promises, then pass the result of promise2 to a bunch of other rxjs operators to do a bunch of different things if promise2 resolves. But I want promise2 to execute only if promise1 resolves. To get this effect, should I be using something like
from(promise1).pipe(
map(x => promise2),
tap((x) => console.log('response from map: ', x)),
other_rxjs_ops)
.subscribe(...)
If I use concat instead of map
concat(promise1, defer(() => promise2)).pipe(
tap(console.log('response from map: ', x)),
other_rxjs_ops))
.subscribe(...)
I'm presuming what will happen is that promise1 will execute, and if it resolves, promise2 will execute, and results from both promises will then go through the other rxjs operators and be subscribed to, right?
So is my approach with map for the desired result appropriate or should I be thinking of another rxjs operator to work with in executing promise1 and promise2? The one issue I run into when using map is that the console.log inside tap seems to execute before the promise inside map has resolved or errored out, and console prints the following:
response from map: undefined //presuming promise2 hasn't had a chance to resolve yet, which is why we're getting undefined because the promise is designed to return a string value.
Using concatMap instead of map addresses this issue, but I don't understand why tap behaves the way it does when used with map. Any guidance on that would be helpful also.

About your 1st option
You are using map operator and seeing tap is not waiting for promise to be resolved.
So what is happening here,map operator doesn't subscribe inner observable so it is returning promise object(Promise {}) and will not wait to be resolved,for that tap operator here logging this - response from map: Promise {}
Any higher order mapping(concatMap mergemap switchMap ExhaustMap) subscribe the inner observable.Know more: link1 | Link2
So in this concatMap(x => promise2) of code,concatMap will subscribe promise2 and will emit data when promise2 get resolved.
1st option using concatMap
from(promise1)
.pipe(
concatMap((x) => promise2),
tap((x) => console.log('response from map: ', x))
// other_rxjs_ops...
)
.subscribe();
In your case source observable which is from(promise1) will emit response once promise1 is resolved.After receiving response from promise1,concatMap will subscribe promise2
One Promise can emit only one data either resolve or reject so you can use mergeMap or any higher order map operator also here.
So you can consider 1st option if you need to use promise1 response to call promise2.
Now coming to your 2nd option,which is using concat.
So you can consider this over 1st option if promise2 is not dependent on response of promise1.
Simply your requirement is to call promise2 after completion of promise1
I am providing a working example,by which you can clear your doubts
import { concat, from, interval, of } from 'rxjs';
import {
concatMap,
exhaustMap,
map,
mergeMap,
switchMap,
take,
tap,
} from 'rxjs/operators';
const source = interval(1000).pipe(take(5));
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise1 resolved!');
// reject('Promise1 rejected!');
}, 5000);
});
/*..............Option 1..............*/
source
.pipe(
mergeMap((result) => {
console.log("Inside pipe: ",result);
return promise1;
})
// concatMap(()=>promise1),
)
.subscribe({
next: console.log,
error: console.error,
complete: () => console.log('Completed!'),
});
// source.pipe(
// tap(console.log),
// map(()=>promise1),
// ).subscribe(console.log);
/*..............Option 2..............*/
concat(source, promise1).subscribe({
next: console.log,
error: console.error,
complete: () => console.log('Completed!'),
});

In your case, you can use one of the Higher-Order RxJS mapping operators, such as concatMap, mergeMap, or switchMap, to merge streams to each other.
You can try something like the following:
from(promise1)
.pipe(
// promise2 won't be executed until the promise1 is resolved.
concatMap((val1) => from(promise2)),
// tap won't be executed until the promise2 is resolved.
tap((val2) => console.log('response from promise2:', val2))
// Other RxJS operators here...
)
.subscribe();
Notes:
The map operator is used to transform the value to another type, and in your case, it's used to transform the promise1 result to a Promise, however, it doesn't subscribe to it nor convert it to an Observable, so the tap is executed directly after transforming it, without waiting it to be resolved.
(Optional) To be able to subscribe to the promise1, it should be transformed to Observable using from function.
To subscribe to the new Observable it should be merged with the original from(promise1) one, using one of the higher-order mapping operators not using the normal map.

Related

Shouldn't fakeAsync prevent automatic subscription completion?

I thought that by using a fakeAsync wrapper, my tests wouldn't automatically run subscriptions and that I'd be controlling that part by calling tick manually, but that doesn't seem to be the case. For example, using this method:
foo(): void {
of([1, 2, 3]).subscribe({
next: () => {
console.info('Subscription completed.')
this.value = true
},
error: (err: unknown) => console.error('error called')
})
}
and testing with this spec:
it('should foo', fakeAsync(() => {
component.foo()
expect(component.value).toBeFalse()
}))
I'm seeing the subscription completed message print and thus the expectation fails. I thought that the foo method would be called, but that the subscription wouldn't complete until I put a tick() call into the spec.
What am I doing wrong?
Your assumption that all observables are asynchronous is wrong.
Observables can either be asynchronous or synchronous depending on the underlying implementation. In other words, reactive streams are synchronous if the source is synchronous unless you explicitly make them asynchronous.
Some examples:
//Synchronous
of(1,2)
.subscribe(console.log);
//asynchronous because of async source
interval(1000)
.subscribe(console.log);
//aynchronous because one stream is async (interval)
of(1,2)
.pipe(
mergeMap(x => interval(1000).pipe(take(2)))
)
.subscribe(console.log);
//async because we make it async
of(1,2, asyncScheduler)
.subscribe(console.log);
TLDR: Because of([1, 2, 3]) is a synchronous source, you won't be able to control when it completes with fakeAsync.
tick is just the way to control the passage of time. from Angular documentation.
If you omit the tickOptions parameter (tick(100))), then tickOptions defaults to {processNewMacroTasksSynchronously: true}.
https://angular.io/api/core/testing/tick

How to modify an Effect to work with multiple requests called?

I've written this Effect to handle one call at a time:
#Effect()
indexCollectiveDocuments$ = this.actions$.pipe(
ofType<IndexCollectiveDocuments>(CollectiveIndexingActionTypes.IndexCollectiveDocuments),
mergeMapTo(this.store.select(getIndexingRequest)),
exhaustMap((request: any[], index: number) => {
return zip(...request.map(item => {
this.currentItem = item;
return this.indexingService.indexDocuments(item).pipe(
map((response: any[]) => new IndexCollectiveDocumentsSuccess(response)),
catchError(error => of(new IndexCollectiveDocumentsFailure({ error: error, item: this.currentItem })))
)
}))
})
);
It does dispatch both Success and Failure actions according to the request result.
But when I feed the effect with multiple items(which are the payload of getIndexingRequest) to send requests one after another, the Success and Failure actions are not dispatched accordingly, cancels when one is failed.
How do I modify it so that it works with multiple requests, rather than one?
EDIT: I can see all the requests and their results in the network tab. But only one action is dispatched.
You're using the RxJS operator exhaustMap which will cancel incoming requests when there is already one request running.
To run all requests use either mergeMap (run parallel) or concatMap (run serial).

correct way to process data from an rxjs oberservable subscription with back-pressure

I have an rxjs.observable (rxjs version 6.2.1) that returns urls I need to make a GET request to.
var subscription = urlObservable$.subscribe(
function (url) {
console.log('new URL: ' + url);
processURL(url)
},
function (err) { console.log('Error: ' + err); },
function () { console.log('Completed'); }
);
For every url I need to make the request via the function processURL(url). What is the correct way, accordingly to the react philosophy, to process all this incoming urls and make the requests one by one instead of firing all of them as soon as the subscribe emits data? Please note that in this case, the observable urlObservable$ will return data much faster than the request that needs to be made with the returned url.
processURL can return a promise.
Thanks.
If urlObservable$ is emitting just strings you can simply use concatMap that always waits until the previous Observable completes:
urlObservable$
.pipe(
concatMap(url => processURL(url)),
)
.subscribe(...);
This will work even if processURL returns a Promise.

Rxjs: "throw" statement not being handled by piped catchError

A "authenticationService" provides the following authenticate method. I'm unable to enter the piped catchError. What am I missing?
authenticate(credentials: { username: string; password: string }){
return new Observable<any>((observer: Observer<any>) => {
// ... calling the service, obtaining a promise
const authenticationPromise = ... ;
// This is a promise, it must be converted to an Observable
authenticationPromise
.then(() => {
observer.next('ok');
observer.complete();
})
.catch(err => {
console.log('service error ' + err);
throw new Error('crap');
});
});
}
Setting all the ngrx & ngrx/effect part aside, this authentication method is called upon user request:
(redux stuff).(({ payload }: actions.LoginRequestAction) =>
context.authService.authenticate(payload)
.pipe(
map(() => new GenericSuccessAction(...))
// Even though a throw statement happened in the authenticate method, this is never reached:
catchError((err: Error) => {
console.log('[debug] error caught: ', err);
return of(new actions.LoginFailureAction());
}),
)
)
As stated here, catchError is used to:
Gracefully handle errors in an observable sequence.
First of all, you are basically handling the error in your promise by catching the error in your promise. Throwing the error in the promise doesn't return an observable that emits an error.
You can:
Convert your promise into an observable and don't use .catch at all.
Return the error as an observable with rxjs' throwError(err)
Either way, the way you create your observable is questionable.
This is a much better and concise way to handle promises in rxjs:
from(authenticationPromise)
.pipe(
map(() => 'ok')
)

rxjs switchMap onerror behavior

I have a confusion about switchMap in rxjs Observable:
for example i have next code:
Observable.fromEvent(button, 'click')
.switchMap(() => Observable.fromPromise(fetch('http://return-error.com')))
.subscribe(
(response) => {
console.log(response);
},
(error) => {
console.log(error);
}
);
If I get error from fetch, subscription is interrupted. So is there way to handle it to not create new subscription for any error?
I have even tried to catch error and return Observable, but subscription is interrupted anyway.
upd: how to deal with angular 2 http.get instead of fetch?
It's always more helpful if you make a Bin when asking these questions. I did it for you on this one.
You simply need to swallow the error in the fetch call. E.g.
fetch('bad-url').catch(err => 'Error but keep going')
Here's the demo. Click the document (output) to fire the event.
http://jsbin.com/vavugakere/edit?js,console,output
(You'll need a browser with native Fetch implementation or it'll throw an error)

Resources