chaining of Observables to queue list of ajax calls - javascript

I have a dynamic array of Ajax URLs and trying to queue the calls in sequence. After finishing the first call successfully, the second ajax call will go or if the result is failed then it will end the execution loop. Like that it should complete the array until the end.
Do we have options for this with RxJS observables?

Example where data is fetched sequentially using concatMap but processed asynchronously using mergeMap.
Codexample at codesandbox.io
import { from } from "rxjs";
import { concatMap, map, catchError, tap, mergeMap } from "rxjs/operators";
const urls = [
"https://randomuser.me/api/",
"https://geek-jokes.sameerkumar.website/api",
"https://dog.ceo/api/breeds/image/random"
];
from(urls)
.pipe(
concatMap(url => {
console.log("=>Fetch data from url", url);
return fetch(url);
}),
tap(response => console.log("=<Got reponse for", response.url)),
mergeMap(response => response.json()),
tap(data => console.log("Decoded response", data))
)
.subscribe(
() => console.log("fetched and decoded"),
e => console.log("Error", e),
() => console.log("Done")
);

Sure, concat is the right creation function for that job. It is passed a list of Observables and completes them in order, one after the other. If any one of them fails, an error notification is sent that can be handled in the subscribe function. The chain is completed immediately after an error, preventing subsequent Ajax calls to be fired.
An example could look like this:
concat(...urls.map(
url => this.http.get(url))
).subscribe(
next => console.log("An Ajax call has finished"),
error => console.log("An Ajax call has gone wrong :-( "),
complete => console.log("Done with all Ajax calls")
)
The documentation for concat reads:
Creates an output Observable which sequentially emits all values from given Observable and then moves on to the next.

Related

RxJs concatMap is emitting only one http stream out of many

I have been trying to get stream of objects in a sequencial order, however concatMap is not working for me. With mergeMap I get the stream but no in a sequencial order.
Below is my code I am trying:
this.result
.pipe(
map((result: Result) => {
return result.contents
.map((content: Content) => this.databaseService
.getResource<Resource>('Type', content.key) // http call in service
)
}),
mergeMap((requests: Observable<Resource>[]) => {
return from(requests)
.pipe(
concatMap((resource: Observable<Resource>) => resource), // ----> Trigger only once.
filter((resource:Resource) => resource.status === 'active'),
skip(this.pageIndex * this.PAGE_SIZE),
take(this.PAGE_SIZE),
)
}),
)
.subscribe({
next: (resource: Resource) => {
console.log(resource) // resource stream
},
complete: () => console.log('completed')
});
concatMap will only process one observable source at a time. It will not process subsequent sources until the current source completes.
In your case the observable from this.databaseService.getResource() is not completing. To force it to complete after the first emission, you could append a take(1) like this:
concatMap((resource: Observable<Resource>) => resource.pipe(take(1))
Alternatively, if the call to databaseService.getResource() is only meant to emit a single value, you could modify your service to emit only a single value.
// http call in service
Note: If you are using the Angular HttpClient, a typical get request will complete when the response is received. So you can probably have your getResource() method return the oservable from the http.get() call.

Cannot get result of first HTTP request using RxJS' switchMap

I am trying to make 2 HTTP requests and in the first call I try to create a record and then according to its results (response from the API method) I want to execute or omit the second call that updates another data. However, although I can catch the error in catchError block, I cannot get the response in the switchMap method of the first call. So, what is wrong with this implementation according to teh given scenario? And how can I get the response of the first result and continue or not to the second call according to this first response?
let result;
let statusCode;
this.demoService.create(...).pipe(
catchError((err: any) => { ... }),
switchMap(response => {
// I am trying to get the response of first request at here
statusCode = response.statusCode;
if(...){
return this.demoService.update(...).pipe(
catchError((err: any) => { ... }),
map(response => {
return {
result: response
}
}
)
)}
}
))
.subscribe(result => console.log(result));
The question is still vague to me. I'll post a more generic answer to make few things clear
There are multiple things to note
When an observable emits an error notification, the observable is considered closed (unless triggered again) and none of the following operators that depend on next notifications will be triggered. If you wish to catch the error notifications inside the switchMap, you could return a next notification from the catchError. Something like catchError(error => of(error)) using RxJS of function. The notification would then be caught by the following switchMap.
You must return an observable from switchMap regardless of your condition. In this case if you do not wish to return anything when the condition fails, you could return RxJS NEVER. If you however wish to emit a message that could be caught by the subscriptions next callback, you could use RxJS of function. Replace return NEVER with return of('Some message that will be emitted to subscription's next callback');
import { of, NEVER } from 'rxjs';
import { switchMap, catchError, map } from 'rxjs/operators';
this.demoService.create(...).pipe(
catchError((err: any) => { ... }),
switchMap(response => {
statusCode = response.statusCode;
if (someCondition) {
return this.demoService.update(...).pipe( // emit `update()` when `someCondition` passes
catchError((err: any) => { ... }),
map(response => ({ result: response }))
);
}
// Show error message
return NEVER; // never emit when `someCondition` fails
}
)).subscribe({
next: result => console.log(result),
error: error => console.log(error)
});
You can implement with iif
this.demoService
.create(...)
.pipe(
// tap first to be sure there's actually a response to process through
tap(console.log),
// You can use any condition in your iif, "response.user.exists" is just a sample
// If the condition is true, it will run the run the update$ observable
// If not, it will run the default$
// NOTE: All of them must be an observable since you are inside the switchMap
switchMap(response =>
iif(() =>
response.user.exists,
this.demoService.update(response.id), // Pass ID
of('Default Random Message')
)
),
catchError((err: any) => { ... })
);

How to make HTTP calls inside a loop and wait for one to finish before making another?

I have an array of items which on each element I want to make an HTTP call, wait for it to finish, then make another call, only one at a time.
I tried:
index(item) {
return this.service.index(item).pipe(
map(response => {
// handle success case
}),
catchError(error => {
// handle error case
})
)
}
async processArray(array) {
const promises = array.map(item => this.index(item));
await Promise.all(promises);
}
proccessArray(array);
Also with NGRX Effects:
#Effect()
effect$ = this.actions$.pipe(
ofType<action>(actionTypes.action),
mergeMapTo(this.store.select(getMyArray)),
flatMap((request: any[]) => {
return zip(...request.map(item => {
return this.service.index(item).pipe(
map(response => {
// handle success case
}),
catchError(error => {
// handle error case
})
)
}))
}),
);
Also tried doing it in for and forEach loops but they fire all the requests at once. How could I achieve this?
If you are using promises and want to wait for each promise to resolve before another call is made then (1) you should not use Promise.all as this will wait til all requests are resolved and (2) you need to use a plain old for-loop which enables you to wait for async operations within the loop.
async processArray(array) {
for(var i = 0; i < array.length; i++){
await yourServiceCall();
}
}
As a sidenote: Since you are using async-await, don't forget to convert your observables to promises.
If you want to move away from promises (and async-await) and rely on pure RxJS instead, have a look at concatMap:
Projects each source value to an Observable which is merged in the output Observable, in a serialized fashion waiting for each one to complete before merging the next.
For example:
import { from } from 'rxjs/observable/from';
ngOnInit() {
from(myArray)
.pipe(concatMap(el => yourServiceCall(el)))
.subscribe(/* your logic */);
}

Catch in Observable stops HTTP Calls from Observable.interval

Observable.interval(10000)
.switchMap(() => this.http.get(url))
.catch (err => Observable.empty())
.subscribe(data => render(data))
Each 10 seconds we make an HTTP call. If an error happens, observable becomes completed, it doesn't make any calls anymore. How to prevent that?
That's correct behavior, when a complete or error notification is sent observers unsubscribe and the chain is disposed.
You can use the retry() operator to resubscribe but it's hard to tell what is your goal from this brief description.
Observable.interval(10000)
.switchMap(() => this.http.get(url))
.retry()
.subscribe(data => render(data))
takeUntil() of observable.
RxJS implements the takeUntil operator. You can pass it either an Observable or a Promise that it will monitor for an item that triggers takeUntil to stop mirroring the source Observable.
for more info click here
Try this:
let dataX = Observable.interval(10000)
.switchMap(() => this.http.get(url));
let caught = dataX.catch(
Observable.return({
error: 'There was an error in http request'
}))
caught.subscribe((data) => { return render(data) },
// Because we catch errors now, `error` will not be executed
(error) => {console.log('error', error.message)}
)
if you want you can put any condition when the error comes like
if(!data[error]){
render(data)
}
I hope that it helps you
Observable.interval(10000)
.switchMap(() => this.http.get(url)
.map(res => res.json())
.catch (err => Observable.empty()))
.subscribe(data => render(data))

How to chain multiple .map() calls on previous items using rxjs

In the example below I was wondering how you would go about preforming two operations on the same response from the .swichMap().
In the example I put the second .map in which is clearly wrong but sort of illiterates what I want to do. How would I go about calling two functions. Also when I break the fist map() out into a function like .map(response => {fn1; fn2;}); typescript throws an error?
#Effect()
getUserCourse$: Observable<Action> = this.actions$
.ofType(userCourse.ActionTypes.LOAD_USER_COURSE)
.map<string>(action => action.payload)
.switchMap(userCourseId => this.userCourseApi.getUserCourse(userCourseId))
.map(response => new userCourse.LoadUserCourseSuccessAction(response.data));
.map(response => new course.LoadCourseSuccessAction(response.course));
For this answer I'm assuming that both functions userCourse.LoadUserCourseSuccessAction and course.LoadCourseSuccessAction do return Observables. If not you can always create one with Rx.Observable.of or Rx.Observable.fromPromise in case of for example an AJAX call.
If I understand you correctly you want to do independent things with the response, but do them in parallel and merge the results back in the stream. Have a look at the following code that shows how this can be archived.
Rx.Observable.of(
{data: 'Some data', course: 'course1'},
{data: 'Some more data', course: 'course2'}
).mergeMap((obj) => {
// These two streams are examples for async streams that require
// some time to complete. They can be replaced by an async AJAX
// call to the backend.
const data$ = Rx.Observable.timer(1000).map(() => obj.data);
const course$ = Rx.Observable.timer(2000).map(() => obj.course);
// This Observable emits a value as soon as both other Observables
// have their value which is in this example after 2 seconds.
return Rx.Observable.combineLatest(data$, course$, (data, course) => {
// Combine the data and add an additinal `merged` property for
// demo purposes.
return { data, course, merged: true };
});
})
.subscribe(x => console.log(x));
Runnable demo

Categories