Concurrent Ajax requests with Rxjs - ajax

I'am currently switched from promises to observables. I am using Redux-Observable for my react app. Basically, I am looking for the best operator that will enable mutliple, concurrent ajax calls and return the responses when all the observables have sucessfully finished executing.
Here is a code snippet from my app.
let epicPostAd = (action$, store, {ajax}) =>
action$.ofType(POST_AD)
.debounceTime(1000)
.mergeMap(({ payload }) =>
ajax(generateAjaxRequestSetting(POSTS_URL, 'post', payload,CT_JSON))
.map(response => postAdSuccessful(response))
.catch(e => Observable.of(postAdUnsuccessful(e.xhr.response)))
.takeUntil(action$.ofType(LOCATION_CHANGE))
)
It is a simple ajax request that posts given ad and dispatches POST_AD_SUCCESSFUL when response is 201 else dispatches POST_AD_UNSUCCESSFUL on error.
But the issues is I want to make subsequent ajax observable stream when there is a response. Such as
.map(response => /* start a stream of ajax observables then process the response */)
I will appreciate if you show me the optimal way of achieving this.

Sounds like you're looking for the forkJoin operator.
It will subscribe to all the Observables you pass to it and after they all complete, it will emit the last value from each inside an array.
It wasn't entirely clear where in your Epic you wanted to do this, so I just made a generic example:
const somethingEpic = (action$, store, { ajax }) =>
action$.ofType(SOMETHING)
.mergeMap(() =>
Observable.forkJoin(
ajax('/first'),
ajax('/second'),
ajax('/third')
)
.do(results => {
// the results is an array, containing each
const [first, second, third] = results;
console.log(first, second, third);
})
.map(results => ({
type: 'SOME_RESULTS',
results
}))
);
Technically, it supports a final resultSelector argument you can use instead of using the map operator after it, but I tend not to use it because I've found it's less clear with only negligible performance benefits in common redux-observable style cases. But it's still good to know. Can be handy for more "data-normalization" stuff rather than "transform this into an action" stuff.
const somethingEpic = (action$, store, { ajax }) =>
action$.ofType(SOMETHING)
.mergeMap(() =>
Observable.forkJoin(
ajax('/first'),
ajax('/second'),
ajax('/third'),
results => ({
type: 'SOME_RESULTS',
results
})
)
);
ALSO, if you're asking yourself "what operator do I use?" you should try the operator wizard located in the documentation: http://reactivex.io/rxjs/
Scroll down to the part that says:
Do you need to find an operator for your problem? Start by choosing an option from the list below:
I have one existing Observable, and...
I have some Observables to combine together as one Observable, and...
I have no Observables yet, and...
Hint: open your DevTools to experiment with RxJS.
Though in this case, forkJoin is correctly suggested but when you click on it, it isn't yet documented :sadface: But a google search would present many different websites explaining what it does and how to use it (in RxJS and in other Rx implementations in other languages). Like this helpful website

Here is the answer to my own question. Although JayPhelps answered, I realized that my question was not so clear. Using Jay's recommendation. I came up with the following:
let epicPostAd = (action$, store, {ajax, observable}) =>
action$.ofType(POST_AD)
.debounceTime(1000)
.mergeMap(({ payload }) =>
ajax(generateAjaxRequestSetting(POSTS_URL, 'post', payload, CT_JSON))
.mergeMap(response =>
observable.forkJoin(
ajax(''),
ajax('')
)
.do(res => {
const [first, second, third] = results;
console.log(first, second, third);
})
.map(res => postAdSuccessful(res))
)
.catch(e => observable.of(postAdUnsuccessful(e.xhr.response)))
.takeUntil(action$.ofType(LOCATION_CHANGE))
)
So here how it works. I make a post request and immediately after ajax request finishes execution I .mergeMap the response to a stream of ajax ovservables using .forkJoin(). Then process the results

Related

ngrx/rxjs: can this code to first check the store and then do the API request be improved?

Before fetching new data from the backend, I'm first checking if it's in the store already, and I do take care that the store is always synced every time there's an update.
To do so, my effect does the following:
#Effect()
LoadImage: Observable<LoadImageSuccess> = this.actions$.pipe(
ofType(AppActionTypes.LOAD_IMAGE),
withLatestFrom(action => this.store$.select(selectImage, action.payload).pipe(map(result => ({ action, result })))),
switchMap(observable => observable),
mergeMap(({ action, result }) =>
result !== null
? of(result).pipe(map(image => new LoadImageSuccess(image)))
: this.imageApiService.getImage(action.payload).pipe(
map(image => new LoadImageSuccess(image)),
catchError(error => EMPTY)
)
)
);
This seems to work fine, but I wonder if it can be made nicer:
The switchMap(observable => observable), doesn't look very nice.
Would the condition in the mergeMap be better handled via a iif?
When two components dispatch a LOAD_IMAGE action, my API call is still happening twice, because the first request hasn't completed yet (and hasn't put the image in the store) when the second request starts. This is not a common occurrence with images on my website, but might be with other components in the future and I wonder if there's a way to improve this.
Thanks!
Please note, if I remove the switchMap(observable => observable),, I get the following error:
The object that gets passed to the mergeMap if I comment out the switchMap, is of type Store:
EDIT:
Based on the accepted answer, this is what I ended up with:
#Effect()
LoadImage: Observable<LoadImageSuccess> = this.actions$.pipe(
ofType(AppActionTypes.LOAD_IMAGE),
mergeMap(action =>
this.store$.select(selectImage, action.payload).pipe(
mergeMap(imageFromStore =>
imageFromStore !== null
? of(imageFromStore).pipe(map(image => new LoadImageSuccess(image)))
: this.imageApiService.getImage(action.payload).pipe(
map(image => new LoadImageSuccess(image)),
catchError(error => EMPTY)
)
)
)
)
);
The problem is that the arg passed to withLatestFrom is the projection function, so that's why you have to resort to the switchMap hack.
If you need to check based on the current action, I think you'd be better off doing it with something like this:
ofType(...),
// do the check
switchMap(
action => this.store.select(...)
.pipe(
...,
// act based on the check's result
// decided to nest this as a response to question 3
// since if multiple calls are made at the same time, only the last one will be good to go
switchMap(({ action, result }) => ...)
)
),

How do I get my observable to have it's values for use in an NGRX effect

To be honest I am a total noob at NGRX and only limited experience in rxjs. But essentially I have code similar to this:
#Effect()
applyFilters = this.actions$.pipe(
ofType<ApplyFilters>(MarketplaceActions.ApplyFilters),
withLatestFrom(this.marketplaceStore.select(appliedFilters),
this.marketplaceStore.select(catalogCourses)),
withLatestFrom(([action, filters, courses]) => {
return [courses,
this.combineFilters([
this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.TRAINING_TYPE),
this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.INDUSTRIES)
])
];
}),
map(([courses, filters]) => {
console.log('[applyFilters effect] currently applied filters =>', filters);
console.log('courseFilters', filters);
const filteredCourses = (courses as ShareableCourse[]).filter(x => (filters as number[]).includes(+x.id));
console.log('all', courses);
console.log('filtered', filteredCourses);
return new SetCatalogCourses(filteredCourses);
})
);
Helper method:
private combineFilters(observables: Observable<number[]>[]): number[] {
if (!observables.some(x => x)) {
return [];
} else {
let collection$ = (observables[0]);
const result: number[] = [];
for (let i = 0; i < observables.length; i++) {
if (i >= 1) {
collection$ = concat(collection$, observables[i]) as Observable<number[]>;
}
}
collection$.subscribe((x: number[]) => x.forEach(y => result.push(y)));
return result;
}
}
So essentially the store objects gets populated, I can get them. I know that the observables of 'this.getCourseIdsFromFiltersByFilterType(args)' do work as on the console log of the 'filters' they are there. But the timing of the operation is wrong. I have been reading up and am just lost after trying SwitchMap, MergeMap, Fork. Everything seems to look okay but when I am trying to actually traverse the collections for the result of the observables from the service they are not realized yet. I am willing to try anything but in the simplest form the problem is this:
Two observables need to be called either in similar order or pretty close. Their 'results' are of type number[]. A complex class collection that has a property of 'id' that this number[] should be able to include. This works just fine when all the results are not async or in a component.(I event dummied static values with variables to check my 'filter' then 'includes' logic and it works) But in NGRX I am kind of lost as it needs a return method and I am simply not good enough at rxjs to formulate a way to make it happy and ensure the observables are fully realized for their values from services to be used appropriately. Again I can see that my console log of 'filters' is there. Yet when I do a 'length' of it, it's always zero so I know somewhere there is a timing problem. Any help is much appreciated.
If I understand the problem, you may want to try to substitute this
withLatestFrom(([action, filters, courses]) => {
return [courses,
this.combineFilters([
this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.TRAINING_TYPE),
this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.INDUSTRIES)
])
];
}),
with something like this
switchMap(([action, filters, courses]) => {
return forkJoin(
this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.TRAINING_TYPE),
this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.INDUSTRIES
).pipe(
map(([trainingFilters, industryFilters]) => {
return [courses, [...trainingFilters, ...industryFilters]]
})
}),
Now some explanations.
When you exit this
withLatestFrom(this.marketplaceStore.select(appliedFilters),
this.marketplaceStore.select(catalogCourses)),
you pass to the next operator this array [action, filters, courses].
The next operator has to call some remote APIs and therefore has to create a new Observable. So you are in a situation when an upstream Observable notifies something which is taken by an operator which create a new Observable. Similar situations are where operators such as switchMap, mergeMap (aka flatMap), concatMap and exhastMap have to be used. Such operators flatten the inner Observable and return its result. This is the reason why I would use one of these flattening operators. Why switchMap in your case? It is not really a short story. Maybe reading this can cast some light.
Now let's look at the function passed to switchMap
return forkJoin(
this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.TRAINING_TYPE),
this.getCourseIdsFromFiltersByFilterType(filters, CatalogFilterType.INDUSTRIES
).pipe(
map(([trainingFilters, industryFilters]) => {
return [courses, [...trainingFilters, ...industryFilters]]
})
This function first executes 2 remote API calls in parallel via forkJoin, then take the result of these 2 calls and map it to a new Array containing both courses and the concatenation of trainingFilters and industryFilters

Tap after observable has been subscribed

Is there a way to add more operations to an observable that has already been subscribed? I tried the below code, which doesn't work, as the One more tap after subscribe part is not executed (code is here)
import { of, timer } from 'rxjs';
import { tap, map, take } from 'rxjs/operators';
const source = timer(1000, 1000);
//transparently log values from source with 'do'
const example = source.pipe(
take(3),
tap(val => console.log(`BEFORE MAP: ${val}`)),
map(val => val + 10),
tap(val => console.log(`AFTER MAP: ${val}`))
);
//'do' does not transform values
//output: 11...12...13...14...15
const subscribe = example.subscribe(val => console.log(val));
example.pipe(
tap(val => console.log(`One more tap after subscribe: ${val}`))
);
The use cas I have in mind is where for example I make an http call, and more than one service needs to be updated with the reponse of the call.
I will take this as what you ultimately want to achieve
The use cas I have in mind is where for example I make an http call, and more than one service needs to be updated with the reponse of the call.
const onExampleCalled=new Subject();
// listen to example called
onExampleCalled.subscribe(console.log)
example.pipe(tap(result=>onExampleCalled.next(result)).subscribe()
I am not quite sure what you try to achieve, but the pipe() function does not alter the source Observable. It just outputs a new Observable that results from the old one and the operators you aplied in the pipe(). Therefor your last line of code is like writing
5;
Meaning you set a value as a statement. Maybe you could reassign your Observable to itself after Transformation (although I am sure, that it would look quite ugly and will be hard to understand for others and should therefor be avoided).
example = example.pipe(
tap(val => console.log(`One more tap after subscribe: ${val}`))
);
Maybe you should go more into detail what you specific usecase is, so that we can find a cleaner solution.

observable event filters and timeout

I am very new Rxjs observable and need help with two questions.
I have this piece of code:
const resultPromise = this.service.data
.filter(response => data.Id === 'dataResponse')
.filter((response: dataResponseMessage) => response.Values.Success)
.take(1)
.timeout(timeoutInSeconds)
.map((response: dataResponseMessage) => response.Values.Token)
.toPromise();
I have following basic questions:
1- How can I change .timeout(timeoutInSeconds) to add a message so that I can debug/log later which response it fails? I looked at .timeout syntax in rxjs and didn't see an option to include any message or something.
2-I know .filter((response: dataResponseMessage) => response.Values.Success) will filter to responses with response.Values.Success but is there a syntax where I can do like this for an observable:
const resultPromise = this.service.data
.filter(response => data.Id === 'dataResponse')
.magicSyntax((response: dataResponseMessage) => {
if (response.Values.Success) {
// do something
} else {
// do something else
}
});
Thank you so much in advance and sorry if these are basic/dumb questions.
First question
If you reach timeout the operator will return you an error which can be caught with .catch operator
const resultPromise = this.service.data
.filter(response => data.Id === 'dataResponse')
.filter((response: dataResponseMessage) => response.Values.Success)
.take(1)
.timeout(timeoutInSeconds)
.catch(e=>{
//do your timeout operation here ...
return Observable.Of(e)
})
.map((response: dataResponseMessage) => response.Values.Token)
.toPromise();
Second question simply replace magicSyntax with map or mergemap depends what you want to return from this operation. it is perfectly fine to do if in side the block.
I'm assuming you are using at least Rxjs version 5.5 which introduced pipeable operators. From the docs - these can "...be accessed in rxjs/operators (notice the pluralized "operators"). These are meant to be a better approach for pulling in just the operators you need than the "patch" operators found in rxjs/add/operator/*."
If you aren't using pipeable operators, instead of passing the operators to pipe() like I did below, you can chain them using the dot notation you use in your example.
I suggest referring to learnrxjs.io for some additional info about the operators in RxJS, paired with examples.
The RxJS team has also created a BETA documentation reference.
Explanation
I assumed the first filter is receiving the response and filtering by response.Id instead of data.Id. If that wasn't a typo, you can keep the filter the same.
I added an extra line between the operators for presentation only.
mergeMap is an operator that takes a function that returns an Observable, which it will automatically subscribe to. I'm returning of() here, which creates an Observable that just emits the value provided to it.
catch was renamed to catchError in RxJS 5.5, and pipeable operators were also added, which add support for the .pipe() operator.
If you don't want to do anything besides logging the error, you can return empty(), which will immediately call complete() on the source Observable without emitting anything. EMPTY is preferred if you are using version 6.
Optional: Instead of using filter() and then take(1), you could use the first() operator, which returns a boolean, just like filter(), and unsubscribes from the source Observable after it returns true once.
import {EMPTY, of} from 'rxjs';
import {catchError, filter, take, mergeMap, timeout} from 'rxjs/operators';
const resultPromise = service.data.pipe(
// I assumed you meant response.Id, instead of data.Id
filter((response: dataResponseMessage) => response.Id === 'dataResponse'),
take(1),
// mergeMap accepts a value emitted from the source Observable, and expects an Observable to be returned, which it subscribes to
mergeMap((response: dataResponseMessage) => {
if (response.Values.Success) {
return of('Success!!');
}
return of('Not Success');
}),
timeout(timeoutInMilliseconds),
// catch was renamed to catchError in version 5.5.0
catchError((error) => {
console.log(error);
return EMPTY; // The 'complete' handler will be called. This is a static property on Observable
// return empty(); might be what you need, depending on version.
})
).toPromise();
1- you can use .do() to console.log your response.
.filter(..).do(response => console.log(response))
2- you can use .mergeMap()

RxJS 5 Timed Cache

I am trying to get time expiry cache to work for an observable that abstracts a "request-response", using postMessage and message events on the window.
The remote window expects a message getItemList and replies to it with a message of type {type: 'itemList', data: []}.
I would like to model the itemList$ observable in such a way that it caches the last result for 3 seconds, so that no new requests are made during that time, however, I cannot think of a way to achieve that in an elegant (read, one observable – no subjects) and succint manner.
Here is the example in code:
const remote = someIframe.contentWindow;
const getPayload = message => message.data;
const ofType = type => message => message.type === type;
// all messages coming in from the remote iframe
const messages$ = Observable.fromEvent(window, 'message')
.map(getPayload)
.map(JSON.parse);
// the observable of (cached) items
const itemList$ = Observable.defer(() => {
console.log('sending request');
// sending a request here, should happen once every 3 seconds at most
remote.postMessage('getItemList');
// listening to remote messages with the type `itemList`
return messages$
.filter(ofType('itemList'))
.map(getPayload);
})
.cache(1, 3000);
/**
* Always returns a promise of the list of items
* #returns {Promise<T>}
*/
function getItemList() {
return itemList$
.first()
.toPromise();
}
// poll every second
setInterval(() => {
getItemList()
.then(response => console.log('got response', response));
}, 1000);
I am aware of the (very similar) question, but I am wondering if anyone can come up with a solution without explicit subjects.
Thank you in advance!
I believe you are looking for the rxjs operator throttle:
Documentation on rxjs github repo
Returns an Observable that emits only the first item emitted by the
source Observable during sequential time windows of a specified
duration.
Basically, if you would like to wait until the inputs have quieted for a certain period of time before taking action, you want to debounce.
If you do not want to wait at all, but do not wish to make more than 1 query within a specific amount of time, you will want to throttle. From your use case, I think you want to throttle

Resources