RxJS retry entire chain - javascript

I read images from a live stream and select a batch periodically.
I then send them to the server for validation. A HTTP error will be thrown if any fail validation. If that occurs I want to get a new batch of images.
this.input.getImages()
.throttleTime(500)
.switchMap(image =>
new Observable<{}>(observer => {
// Some operation
})
.map(i => ({ image, i }))
).filter(({ i }) => {
// some filtering
})
.map(({ image }) => image)
.take(6)
.bufferCount(6)
.map(images => // switch map??
Observable.fromPromise(this.server.validate(images))
)
.retry(2) // This only retrys the request, I want it to retry the whole chain (to get valid images)
.subscribe(images => {
console.log('All done')
},
err => {console.log(err)}
)
The problem I'm having is that only the HTTP request gets retried since that is the new observable. There must be some way to encapsulate the beginning of the chain into a single Observable?

See learnrxjs - retry. The example shows everything restarting from source onwards when an error is thrown.
The page shows pipe syntax, but the JSBin shows fluid operator syntax if you prefer.
The basic pattern is
const retryMe = this.input.getImages()
.flatMap(val => {
Observable.of(val)
// more operators
})
.retry(2);

Simple way is to wrap your complex observable in defer ans use retry on resulting observable.

Related

RXJS Chain dependent observables sequentially, but getting each emission for a progress bar

I'm facing a problem, and I've been trying to find a solution using RxJs, but can't seem to find one that fits it...
I have 3 different REST requests, that will be called sequentially, and each of them needs the response of the previous one as an argument
I want to implement a progress bar, which increments as the requests succeed
Here is what I thought :
I am going to use pipes and concatMap() to avoid nested subscriptions and subscribe to each request when the previous one is done.
Consider this very simplified version. Assume that each of represents a whole REST successful request (will handle errors later), and that I will do unshown work with the n parameter...
const request1 = of('success 1').pipe(
delay(500),
tap(n => console.log('received ' + n)),
);
const request2 = (n) => of('success 2').pipe(
delay(1000),
tap(n => console.log('received ' + n))
);
const request3 = (n) => of('success 3').pipe(
delay(400),
tap(n => console.log('received ' + n))
);
request1.pipe(
concatMap(n => request2(n).pipe(
concatMap(n => request3(n))
))
)
However, when I subscribe to the last piece of code, I will only get the response of the last request, which is expected as the pipe resolves to that.
So with concatMap(), I can chain my dependent REST calls correctly, but can't follow the progress.
Though I could follow the progress quite easily with nested subscriptions, but I am trying hard to avoid this and use the best practice way.
How can I chain my dependent REST calls, but still be able to do stuff each time a call succeeds ?
This is a generalized solution, though not as simple. But it does make progress observable while still avoiding the share operator, which can introduce unexpected statefulness if used incorrectly.
const chainRequests = (firstRequestFn, ...otherRequestFns) => (
initialParams
) => {
return otherRequestFns.reduce(
(chain, nextRequestFn) =>
chain.pipe(op.concatMap((response) => nextRequestFn(response))),
firstRequestFn(initialParams)
);
};
chainRequests takes a variable number of functions and returns a function that accepts initial parameters and returns an observable that concatMaps the functions together as shown manually in the question. It does this by reducing each function into an accumulation value that happens to be an observable.
Remember, RxJS leads us out of callback hell if we know the path.
const chainRequestsWithProgress = (...requestFns) => (initialParams) => {
const progress$ = new Rx.BehaviorSubject(0);
const wrappedFns = requestFns.map((fn, i) => (...args) =>
fn(...args).pipe(op.tap(() => progress$.next((i + 1) / requestFns.length)))
);
const chain$ = Rx.defer(() => {
progress$.next(0);
return chainRequests(...wrappedFns)(initialParams);
});
return [chain$, progress$];
};
chainRequestsWithProgress returns two observables - the one that eventually emits the last response, and one that emits progress values when the first observable is subscribed to. We do this by creating a BehaviorSubject to serve as our stream of progress values, and wrapping each of our request functions to return the same observable they normally would, but we also pipe it to tap so it can push a new progress value to the BehaviorSubject.
The progress is zeroed out upon each subscription to the first observable.
If you wanted to return a single observable that produced the progress state as well as the eventual result value, you could have chainRequestsWithProgress instead return:
chain$.pipe(
op.startWith(null),
op.combineLatest(progress$, (result, progress) => ({ result, progress }))
)
and you'll have an observable that emits an object representing the progress toward the eventual result, then that result itself. Food for thought - does progress$ have to emit just numbers?
Caveat
This assumes request observables emit exactly one value.
The simplest solution would be to have a progress counter variable that is updated from a tap when each response comes back.
let progressCounter = 0;
request1.pipe(
tap(_ => progressCounter = 0.33),
concatMap(n => request2(n).pipe(
tap(_ => progressCounter = 0.66),
concatMap(n => request3(n)
.pipe(tap(_ => progressCounter = 1)))
))
);
If you want the progress itself to be observable then you want to share the request observables as to not make duplicate requests) and then combine them to get the progress.
An example of how you may want to approach that can be found at: https://www.learnrxjs.io/recipes/progressbar.html

Rxjs sequence of ajax calls depending on results from previous ones and handling errors

can you help me with turning the following promise-based scenario into rxjs streams?
async function createItem(...) {
let newData, metadata;
try {
newData = await ajax('.../createItem', ...)
} catch (e) {
throw new Error('Can not create item', e);
}
try {
metadata = await ajax('.../createMetadata', newData.id, ...);
} catch (e) {
throw new Error('Item created but metadata not', newData, e);
}
return {newData, metadata}
}
Is's just the flow when you have two-staged item creation process.
For example, you first call ajax to create actual item, you get item id from the response
then you call another ajax to set some metadata of newly created item.
When success, you return the new metadata and new data. When saving actual item (first stage) fails, you get the error that neither actual object nor metadata was created.
If the actual object is created but metadata fails to save, you will get error that actual object was created however metadata not.
I am trying to implement that using Observable.concat, however I can't access the output from first ajax call in the second call.
I also tried with Observable.ajax(...).mergeMap(Observable.ajax(...))....
but then I don't know where the catch should be put to identify at which stage (1 or 2) the failure occured.
How you generally solve the problem, when you have sequence of events (where input of previous is needed for the next one) and want to produce full result combined from all ajax responses, or partial result combined with responses from first subsequent success ajax calls and the error message of the stage when it failed?
I'm assuming you're using RxJS 5.5 with pipable operators:
return ajax('.../createItem', ...)
.pipe(
catchError(e => {
throw new Error('Can not create item', e);
}),
concatMap(newData => ajax('.../createMetadata', newData.id, ...)
.pipe(
map(metadata => ({ newData, metadata })),
catchError(e => {
throw new Error('Item created but metadata not', newData, e);
}),
)
),
)

Observable - 401 causing forkJoin to error out

I am using forkJoin to make several server requests. This is a pattern I have commonly been using through out my application and it has been working great. However we just started implementing user roles which is done on the backend. I am not sure what is the best practice for implementing roles as I am mostly a front end developer, nonetheless this is the problem I have encountered:
Our application has member and admin member roles.
From each view I must make calls to the backend for both member and admin member roles regardless as roles are not determined on the frontend.
Member data is always returned in for both roles as members and admin members both have personal data.
Requests made for admin data is only returned when the user is an admin. Whenever the user does not have admin access the request returns a 401 error. This is where I am having a problem.
Whenever the call returns a 401, the error method in my subscribe method is invoked and I do not have access to any of the calls that were made including the calls associated to the member data.
In my included code within the forkJoin there are five calls passed into the method. The third and forth call only return data if the user is an admin while the rest of the calls are always returned for either member or admin.
When the user is not an admin the third call returns a 401 and the stream stops and the error handler in my subscribe method is invoked. This is obviously not what I want. I want the stream to continue so I can use the data in the _data method.
I have only been using RXJS for 6 months and am learning. Maybe I should be using a different pattern or maybe there is a way to fix this. Any help with code examples would be greatly appreciated. Below my code example I included another example of code in which I attempted to fix the problem by playing around with catch methods. It didn't work.
My View get method:
private getZone() {
this.spinner.show();
this.zonesService.getZone(this.zoneId)
.map(response => {
this.zone = response['group'];
return this.zone;
})
.flatMap(() => {
return Observable.forkJoin(
this.teamsService.getTeam(this.zone['TeamId']),
this.zonesService.getZoneAssociations(this.zone['id'], '/myDevices'),
this.zonesService.getZoneAssociations(this.zone['id'], '/devices'),
this.zonesService.getZoneAssociations(this.zone['id'], '/groupMembers'),
this.sitesService.getSite(this.zone['SiteId'])
);
})
.subscribe(
_data => {
// data handling...
},
_error => {
// error handling ...
}
);
}
My attempt to fix:
private getZone() {
this.spinner.show();
this.zonesService.getZone(this.zoneId)
.map(response => {
this.zone = response['group'];
return this.zone;
})
.flatMap(() => {
return Observable.forkJoin(
this.teamsService.getTeam(this.zone['TeamId']),
this.zonesService.getZoneAssociations(this.zone['id'], '/myDevices'),
this.zonesService.getZoneAssociations(this.zone['id'], '/devices')
.catch(error => Observable.throw(error)),
this.zonesService.getZoneAssociations(this.zone['id'], '/groupMembers')
.catch(error => Observable.throw(error)),
this.sitesService.getSite(this.zone['SiteId'])
);
})
.subscribe(
_data => {
// data handling...
},
_error => {
// error handling...
}
);
}
Returning Observable.throw will just rethrow the caught error, which will see forkJoin emit the error.
Instead, you could use Observable.of(null) to emit null and then complete, which will see forkJoin emit a null for the observable that emitted the error:
return Observable.forkJoin(
this.teamsService.getTeam(this.zone['TeamId']),
this.zonesService.getZoneAssociations(this.zone['id'], '/myDevices'),
this.zonesService.getZoneAssociations(this.zone['id'], '/devices')
.catch(error => Observable.of(null)),
this.zonesService.getZoneAssociations(this.zone['id'], '/groupMembers')
.catch(error => Observable.of(null)),
this.sitesService.getSite(this.zone['SiteId'])
);
Or, if you wanted to emit the error as a value, you could use Observable.of(error).

Map each item of a stream to a promise and return its value

I am trying to map each item of a stream to a promise, something like this
myStream$
.flatMap(id => Rx.Observable.fromPromise(database.get(id)))
.subscribe(val => console.log(val));
myStream$ has about 15 items. Since none of these items can be found in the database, each promise will be rejected. I was expecting 15 log outputs printing an error. However all I get is one single error
rx.js:77 Uncaught {"status":404,"name":"not_found","message":"missing","reason":"missing"}
Why am I only getting one error instead of 15?
This behavior is expected - whenever an error is thrown, the stream will finalize (in other words: "stop & unsubscribe all subscribers").
If you want your stream to complete properly regardless of one or all db-request failing, you have to handle the error inside the sub-stream that wraps the promise, since a reject will be evaluated as an error in RxJS.
Rx.Observable.from([1,2,3,4,5])
.flatMap(
id => mockRequest(id)
.catch(error => {
console.error(error);
return Rx.Observable.empty(); // here we just return an empty stream, so the "main"-stream won't receive the error and continue with the other ids in the queue
})
)
.subscribe(
val => console.log(val),
error => console.error("Stream hit an error and will finalize", error),
complete => console.log("Done!")
);
function mockRequest(id) {
return Rx.Observable.throw("Request failed for: " + id);
}
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
Note: Instead of return Rx.Observable.empty(); you can of course return any fallback-value via Observable.of("myFallbackValue")
Sidenote & suggestion: You'll have it easier when you make your rest-calls directly with RxJS and not have to wrap a promise. (though technically both are perfectly valid ways)

How to do the chain sequence in rxjs

I would like to to something like:
this._myService.doSomething().subscribe(result => {
doSomething()
});
.then( () => dosthelse() )
.then( () => dosanotherthing() )
So I would like to chain .then like in promise. How would I do that in Rxjs?
this._myService.getLoginScreen().subscribe( result => {
window.location.href = MyService.LOGIN_URL;
/// I would like to wait for the site to load and alert something from the url, when I do it here it alerts the old one
});
.then (alert(anotherService.partOfTheUrl())
getLoginScreen() {
return this.http.get(myService.LOGIN_URL)
.flatMap(result => this.changeBrowserUrl())
.subscribe( result => //i want to do sth when the page is loaded//);
}
changeBrowserUrl(): Observable<any> {
return Observable.create( observer => {
window.location.href = myService.LOGIN_URL;
observer.next();
});
}
The equivalent of then for observables would be flatMap. You can see some examples of use here :
RxJS Promise Composition (passing data)
Why we need to use flatMap?
RxJS sequence equvalent to promise.then()?
For your example, you could do something like :
this._myService.doSomething()
.flatMap(function(x){return functionReturningObservableOrPromise(x)})
.flatMap(...ad infinitum)
.subscribe(...final processing)
Pay attention to the types of what your functions return, as to chain observables with flatMap you will need to return a promise or an observable.
If dosthelse or dosanotherthing returns a raw value, the operator to use is map. If it's an observable, the operator is flatMap (or equivalent).
If you want to do something imperatively. I mean outside the asynchronous processing chain, you could leverage the do operator.
Assuming that dosthelse returns an observable and dosanotherthing a raw object, your code would be:
this._myService.doSomething()
.do(result => {
doSomething();
})
.flatMap( () => dosthelse() )
.map( () => dosanotherthing() );
Notice that if you return the return of the subcribe method, it will correspond to a subscription object and not an observable. A subscription object is mainly for being able to cancel the observable and can't take part of the asynchronous processing chain.
In fact, most of the time, you subscribe at the end of the chain.
So I would refactor your code this way:
this._myService.getLoginScreen().subscribe( result => {
window.location.href = MyService.LOGIN_URL;
/// I would like to wait for the site to load and alert something from the url, when I do it here it alerts the old one
alert(anotherService.partOfTheUrl()
});
getLoginScreen() {
return this.http.get(myService.LOGIN_URL)
.flatMap(result => this.changeBrowserUrl())
.do( result => //i want to do sth when the page is loaded//);
}
changeBrowserUrl(): Observable<any> {
return Observable.create( observer => {
window.location.href = myService.LOGIN_URL;
observer.next();
});
}
Updated rxjs solution
Rxjs has changed quite a bit since this was answered.
flatMap is now mergeMap
Or switchMap, they're mostly interchangeable but it's good to know the difference
.do() is now tap()
Chaining is now done inside of a .pipe(). All manipulation should be done inside this pipe
You can chain pipes if needed (Ex. one variable maps an array of Users. Another variable takes that first variable and maps it a second time)
Do something after the original call has been made
Scenario
Make an HTTP call (Ex. Authentication check)
When that call has finished, navigate to another page
this._myService.getAuthenticated()
.pipe(
tap(result => this._myService.navigateToHome())
)
.subscribe()
Chain multiple calls
Scenario
Make an HTTP call (Ex. Authentication check)
Make a 2nd call to pull more info
Navigate after both calls have finished
this._myService.getAuthenticated()
.pipe(
// The Authentication call returns an object with the User Id
switchMap(user => this._myService.getUserInfo(user.id))
// After the user has been loaded, navigate
tap(user => this._myService.navigateToHome())
)
.subscribe()
Note on the above examples: I am assuming these calls are HTTP which unsubscribe after being called once. If you use a live observable (ex. a stream of Users), make sure you either unsubscribe or use takeUntil/first operators.
Example for Clarification (April, 2022)
The top of this pipe can emit n values (this means the chain will be called everytime a new value enters into the top of the pipe). In this example, n equals 3. This is a key difference between observables and promises. Observables can emit multiple values over time, but a promise cannot.
The subsequent chained streams emit one value (hence mimicing promises).
// Emit three values into the top of this pipe.
const topOfPipe = of<string>('chaining', 'some', 'observables');
// If any of the chained observables emit more than 1 value
// then don't use this unless you understand what is going to happen.
const firstObservable = of(1);
const secondObservable = of(2);
const thirdObservable = of(3);
const fourthObservable = of(4);
const addToPreviousStream = (previous) => map(current => previous + current);
const first = (one) => firstObservable.pipe(addToPreviousStream(one));
const second = (two) => secondObservable.pipe(addToPreviousStream(two));
const third = (three) => thirdObservable.pipe(addToPreviousStream(three));
const fourth = (four) => fourthObservable.pipe(addToPreviousStream(four));
// Pipeline of mergeMap operators, used for chaining steams together.
topOfPipe.pipe(
mergeMap(first),
mergeMap(second),
mergeMap(third),
mergeMap(fourth),
).subscribe(console.log);
// Output: chaining1234 some1234 observables1234
You could also use concatMap or switchMap. They all have subtle differences. See rxjs docs to understand.
mergeMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/mergemap
concatMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/concatmap
switchMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/switchmap

Categories