RxJs call observable on error and repeat steps - javascript

I have an observable that submits the form submit$. This observable may end up with error with status code 403 what means that a user is not authorised and has to log in first.
Is there a way where I could on specific error code invoke another observable which performs an authorisation process. When authorisation is succeeded I want to repeat the submit$ without having user to invoke that observable once again.
To illustrate steps I want to have:
A user tries to submit and submit$ is being subscribed
This ends up with error with status code 403
Observable calls another authorise$ observable which has own workflow
When authorise$ succeeds the submit$ is invoked again
The process completes with success or error
If There is an error during authorise$ abort the submit$ process

I tried an approach here where I am separating into two observables, submit$ and authenticationSubmit$, and then I merge them again. I haven't tested it, and I am writing http.post(...) twice, so it is not exactly as you described it.
import { merge } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
...
const submit$ = http.post(...);
const authenticationAndSubmit$ = submit$.pipe(
filter(httpResponse => httpResponse.status === 403),
switchMap(() => authService.login()),
filter(authResult => authResult === 'success'),
switchMap(() => http.post(...))
);
merge(submit$, authenticationAndSubmit$)
.pipe(
filter(httpResponse => httpResponse.status === 200),
)
.subscribe(httpResponse => {
// Do something
});

Related

Catching Error in pipe() method of an Observable - rxjs/Axios

I refactored my http layer to go from a promised-based implementation to observables using rxjs. The problem that I am facing is that the code crashes whenever server response is 400 or 500,
Axios.request(config).pipe(map(((response: AxiosResponse) => response), catchError(e => {
return new Observable(e);
})));
The problem I am facing is that the error is not being handled by the catchError callback. I am looking for a way in which the error is handled by the catchError callback so that the response can be handled gracefully.
I don't know your problem is this but you can do this:
import { EMPTY } from 'rxjs';
Axios.request(config).pipe(map(((response: AxiosResponse) => response),
catchError(() => EMPTY)));
EMPTY is an object that is imported from rxjs libaray.
It looks like you want to turn the error notification to next notification so you need to use return of(e) eventually EMPTY if you want to suppress the error.
new Observable(e) is probably what throws the error in your case because the parameter passed to Observable needs to be a function which e is not in this case.

Any Scenario(s) that RXJS Observable Subscribe wont trigger either success and error

This is more like question than resolve a problem.
I would like to know if there any scenario that both "Success" and Error" is not triggered.
The post call to "/logout" will result Http status return code 200
with empty respond body which is expected
import { httpClient } from angular/common/http;
private http: HttpClient;
this.http.post<any>('/logout', {})
.subscribe(
() => {
console.log("Logout");
}, error => {
console.log(error);
},
() => {
console.log("Finally");
});
It will output "Finally" 100% of time. That means success and error is not triggered at all.
Is there possibilities that either success and error not trigger. And clearly the http status code response is 200 OK.
Update:
The answer that #meriton provided work great.
Observable, in general, are not required to complete or error. They may remain live, and continue to emit values, forever.
However, Observable returned by HttpClient are guaranteed to terminate with either success or error (though the error may take a few minutes in case of a timeout) according to the HTTP status of the response. The presence of absence of a body does not affect this. If the request is successful, the observable will emit exactly one value: the response body (or null if the response body is absent).
I can not reproduce your claim that "success or error is not triggered at all". May you have misunderstood what the callbacks mean? When you provide three callbacks to subscribe, they are, in order:
the next callback, which receives emitted values
the error callback, which notifies that the Observable has aborted due to an error
the complete callback, which notifies that the Observable has completed successfully
The danger of mixing up callbacks is one reason why the RXJS team has deprecated passing several callbacks as separate arguments to subscribe in RXJS 8. The future proof way to write your code would be:
this.http.post<any>('/logout', {}).subscribe({
complete: () => {
console.log("Logout successful")
},
error: (error) => {
console.log(error);
}
});
BTW, none of these callbacks mean "finally", as in the finally clause of a try-statement, which is executed both in case of success and error. If you want to do something irrespective of whether the Observable completed successfully or failed with an error, you could use the finalize operator.
http library success depends on Status:200, it does not require message.body to be present
Example code of using RXJS pipe flow, where you can control the flow by capturing success & error, controlling timeout. It also demonstrates how you can use .subscribe() method as classic Finally
Example RXJS Flow:
this.http
.post<any>('/logout',{})
.pipe(
map(() => { // OK
return { success: true, err: null };
}),
timeout(10000), // CONTROL TIMEOUT
catchError((e) => { // IN CASE OF ERROR
return of({success: false, err:e});
})
)
.subscribe((result) => { // FINALLY here
if (result.success) {
console.log('Logged out successfully');
} else {
console.log('Logout failed', result.err);
}
});

Reset RxJS concatMap without completing the outter Subject

I'm using a concatMap to handle multiple requests to an API, where I want each request batch to be completed before the next batch is processed. The concatMap works as expected when triggering the flow with callSubject.next(requestData)
The problem: for certain types of requestData I want to cancel any in-flight http calls, and reset the concatMap. Cancelling the httpClient calls that are occurring within the getAll function is handy enough (I have a takeUntil that does that - not shown), but the concatMap may still have a number of queued up requests that will then be processed.
Is there a way to reset the concatMap without completing the callSubject Subject?
Note: if I trigger unsubscribeCallSubject$.next() this clears the concatmap, but also completes the callSubject, which means it can no longer be used with callSubject.next(reqData)
// callSubject is a Subject which can be triggered multiple times
callSubject
.pipe(
concatMap((req) => {
// getAll makes multiple httpClient calls in sequence
return getAll(req).pipe(
catchError((err) => {
// prevent callSubject completing on http error
return of(err);
})
);
}),
takeUntil(unsubscribeCallSubject$)
)
.subscribe(
(v) => log("callSubject: next handler", v),
(e) => log("callSubject: error", e),
() => log("callSubject: complete")
);
If I understand the problem right, you could try an approach which uses switchMap any time unsubscribeCallSubject$ emits.
The code would look something like this
unsubscribeCallSubject$.pipe(
// use startWith to start the stream with something
startWith('anything'),
// switchMap any time unsubscribeCallSubject$ emits, which will unsubscribe
// any Observable within the following concatMap
switchMap(() => callSubject$),
// concatMap as in your example
concatMap((req) => {
// getAll makes multiple httpClient calls in sequence
return getAll(req).pipe(
catchError((err) => {
// prevent callSubject completing on http error
return of(err);
})
);
}),
)
.subscribe(
(v) => log("callSubject: next handler", v),
(e) => log("callSubject: error", e),
() => log("callSubject: complete")
);
To be honest I have not tested this approach and so I am not sure whether it solves your problem, but if I have understood your problem right, this could work.

Vuex 2.0 Dispatch versus Commit

Can someone explain when you would use a dispatch versus a commit?
I understand a commit triggers mutation, and a dispatch triggers an action.
However, isn't a dispatch also a type of action?
As you rightly said, $dispatch triggers an action, and commit triggers a mutation. Here is how you can use these concepts:
You always use $dispatch from your methods in routes / components. $dispatch sends a message to your vuex store to do some action. The action may be done anytime after the current tick, so that your frontend performance is not affected.
You never commit from any of your components / routes. It is done only from within an action, and only when you have some data to commit. Reason: commit is synchronous and may freeze your frontend till it is done.
Let's consider this case: If you have to fetch some json data from server. In this case, you need to do this asynchronously so that your user interface is not unresponsive / frozen for a while. So, you simply $dispatch an action and expect it to be done later. Your action takes up this task, loads data from server and updates your state later.
If you need to know when an action is finished, so that you can display an ajax spinner till then, you may return a Promise as explained below (example: load current user):
Here is how you define the "loadCurrentUser" action:
actions: {
loadCurrentUser(context) {
// Return a promise so that calling method may show an AJAX spinner gif till this is done
return new Promise((resolve, reject) => {
// Load data from server
// Note: you cannot commit here, the data is not available yet
this.$http.get("/api/current-user").then(response => {
// The data is available now. Finally we can commit something
context.commit("saveCurrentUser", response.body) // ref: vue-resource docs
// Now resolve the promise
resolve()
}, response => {
// error in loading data
reject()
})
})
},
// More actions
}
In your mutations handler, you do all the commits originating from actions. Here is how you define the "saveCurrentUser" commit:
mutations: {
saveCurrentUser(state, data) {
Vue.set(state, "currentUser", data)
},
// More commit-handlers (mutations)
}
In your component, when it is created or mounted, you just call the action as shown below:
mounted: function() {
// This component just got created. Lets fetch some data here using an action
// TODO: show ajax spinner before dispatching this action
this.$store.dispatch("loadCurrentUser").then(response => {
console.log("Got some data, now lets show something in this component")
// TODO: stop the ajax spinner, loading is done at this point.
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
Returning a Promise as shown above is entirely optional and also a design decision not preferred by everyone. For a detailed discussion on whether to return a Promise or not, you may read the comments under this answer: https://stackoverflow.com/a/40167499/654825

How can i retry after catch?

How can i retry after catch ?
I want to retry my observable after catch method automatically without calling another subscribe. How can i do that ?
I have something like this for now:
intercept(observable: Observable<Response>): Observable<Response> {
return observable.catch((err, source) => {
return this.refreshToken()
.flatMap(res => {
if (res.status === 200) {
return observable;
}
return Observable.throw(new Error('Can\'t refresh the token'));
});
});
}
So when i have my observable and call subscribe on it it will catch error -> refresh token -> and then return observable where i have to call another subscribe. I dont want to do that. how can i make it work with that subscribe before ?
The call example will looke something like this:
let request = this.http.request(url);
intercept(request).subscribe(res => { //do something });
There are a couple of operators which can be used in conjunction with catch to handle retrial/repetition. They are useful operators, they allow you to conditionally resubscribe to observables which have terminated. From the official documentation for retryWhen :
Repeats the source observable sequence on error when the notifier
emits a next value. If the source observable errors and the notifier
completes, it will complete the source sequence.
Additional info here :
retryWhen,
repeatWhen
You can find some examples here from previous questions:
Using `retryWhen` in http requests
How to retry only on certain error emitted by the source observable in RxJs
How to build an rx poller that waits some interval AFTER the previous ajax promise resolves?
From the second SO question :
src.retryWhen(function (errors) {
// retry for some errors, end the stream with an error for others
return errors.do(function (e) {
if (!canRetry(e)) {
throw e;
}
});
});

Categories

Resources