Observer.error is not sending the error - rxjs

I am trying to generate an intermediate service between a request service and the angular component that subscribes to it. To achieve that, I generate an observable from httpClient request that connects with the intermediate service, and sends the HTTP response.
When the response arrives to the intermediate service, I generate another observable that connects with the client component.
The problem starts here. As I get a 500 status response from request, I get that response in the intermediate service, but when I try to send to the client component with a subscribe.error(err) it starts a loop that never ends, getting the following error:
You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
I am using the same observer.error(err) in the request service and in the intermediate service, and it works correctly only in the request.
Request:
Observable.create((observer: Observer<any>) =>
this.httpClient
.request(httpRequest)
.subscribe(
...
(res: HttpErrorResponse) => {
observer.error(res);
observer.complete();
},
() => {
observer.complete();
},
}
Intermediate Service:
Observable.create((observer: Observer<any>) =>
this.requestService.request(requestServiceOptions).subscribe(
...
(err: HttpErrorResponse) => {
if (err.status === 401) {
this.callbackWhenRequestFails();
} else {
observer.error(err);
observer.complete();
}
}

It has been solutionated.
Unfortunately, when catching the error I was using return instead of throw, then that's why the console was showing the stream error!

Related

handle http errors in rxjs lastValueFrom

I have started to use the lastValueFrom() to handle my observable http requests. One of these http calls is returning an error from the server-side, and I am getting the name: 'EmptyError', message: 'no elements in sequence' message
const response = await lastValueFrom(
this.service.doStuff()
).catch((e) => {
console.log('#1', e);
return {} as DoStuffResponse;
});
at #1 the error is EmptyError , not the error from the http call.
I understand why I am getting it (the http observable does not return a value)
However, what I'd like to know is what the actual error is (a 422 unprocessable entity in this case)
Is this possible ?
I have not found a method to catch the Http errors from lastValueFrom and this does not appear to be documented in rxjs.
As a result I've decided to avoid lastValueFrom when doing Http requests. Instead I stick with Subscribe and always get the Http response codes in the error.
As an aside, many people noticed Subscribe as being deprecated in VSCode, but this is not true; just a certain overload. I know you didn't mention the deprecation as a motivator for using lastValueFrom, but I think most people, including myself, used the VSCode deprecation alert as a prompt to switch.
That said, this Subscribe syntax is still valid and should give you the Http error code:
this.someService.getResults.subscribe(
{
next: (results) => {
console.log('Here are the results...', results);
},
error: (err: any) => {
console.log('Here is the error...', err.status);
},
complete: () => { }
});

RXJS retry based on another subscriptions result

I'm calling an endpoint which needs an authentication on the server.
/users/:userId/my/endpoint => getData(): Observable<any>
The server responds with an error, if the authentication is expired. Then i'm able to refresh this authentication with a client secret i'm sending by POST
/refresh/me => refreshAuth(): Observable<any>
After that, i'll send the first request again and get a successful response (happy case).
How can i achieve this with a single Observable? I know, there is for example catchError and retry.
getData()
.pipe(
catchError(e => {
if (error.code === 1224) {
return refreshAuth();
}
return throwError(error);
}),
retry(1)
).subscribe();
But this would retry every error case, right? How can i retry the whole chain once on the specific auth expired case?
This is how it might be implemented:
getData()
.pipe(
retryWhen(error => error.pipe(
exhaustMap(e => (e.code == 1224) ? refreshAuth() : throwError(e))),
take(1)
)
)
.subscribe();

Axios Reponse Interceptor : unable to handle an expired refresh_token (401)

I have the following interceptor on my axios reponse :
window.axios.interceptors.response.use(
response => {
return response;
},
error => {
let errorResponse = error.response;
if (errorResponse.status === 401 && errorResponse.config && !errorResponse.config.__isRetryRequest) {
return this._getAuthToken()
.then(response => {
this.setToken(response.data.access_token, response.data.refresh_token);
errorResponse.config.__isRetryRequest = true;
errorResponse.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
return window.axios(errorResponse.config);
}).catch(error => {
return Promise.reject(error);
});
}
return Promise.reject(error);
}
);
The _getAuthToken method is :
_getAuthToken() {
if (!this.authTokenRequest) {
this.authTokenRequest = window.axios.post('/api/refresh_token', {
'refresh_token': localStorage.getItem('refresh_token')
});
this.authTokenRequest.then(response => {
this.authTokenRequest = null;
}).catch(error => {
this.authTokenRequest = null;
});
}
return this.authTokenRequest;
}
The code is heavily inspired by https://github.com/axios/axios/issues/266#issuecomment-335420598.
Summary : when the user makes a call to the API and if his access_token has expired (a 401 code is returned by the API) the app calls the /api/refresh_token endpoint to get a new access_token. If the refresh_token is still valid when making this call, everything works fine : I get a new access_token and a new refresh_token and the initial API call requested by the user is made again and returned correctly.
The problem occurs when the refresh_token has also expired.
In that case, the call to /api/refresh_token returns a 401 and nothing happens. I tried several things but I'm unable to detect that in order to redirect the user to the login page of the app.
I found that in that case the if (!this.authTokenRequest) statement inside the _getAuthToken method returns a pending Promise that is never resolved. I don't understand why this is a Promise. In my opinion it should be null...
I'm a newbie with Promises so I may be missing something !
Thanks for any help !
EDIT :
I may have found a way much simpler to handle this : use axios.interceptors.response.eject() to disable the interceptor when I call the /api/refresh_token endpoint, and re-enable it after.
The code :
createAxiosResponseInterceptor() {
this.axiosResponseInterceptor = window.axios.interceptors.response.use(
response => {
return response;
},
error => {
let errorResponse = error.response;
if (errorResponse.status === 401) {
window.axios.interceptors.response.eject(this.axiosResponseInterceptor);
return window.axios.post('/api/refresh_token', {
'refresh_token': this._getToken('refresh_token')
}).then(response => {
this.setToken(response.data.access_token, response.data.refresh_token);
errorResponse.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
this.createAxiosResponseInterceptor();
return window.axios(errorResponse.config);
}).catch(error => {
this.destroyToken();
this.createAxiosResponseInterceptor();
this.router.push('/login');
return Promise.reject(error);
});
}
return Promise.reject(error);
}
);
},
Does it looks good or bad ? Any advice or comment appreciated.
Your last solution looks not bad. I would come up with the similar implementation as you if I were in the same situation.
I found that in that case the if (!this.authTokenRequest) statement inside the _getAuthToken method returns a pending Promise that is never resolved. I don't understand why this is a Promise. In my opinion it should be null...
That's because this.authTokenRequest in the code was just assigned the Promise created from window.axios.post. Promise is an object handling kind of lazy evaluation, so the process you implement in then is not executed until the Promise was resolved.
JavaScript provides us with Promise object as kind of asynchronous event handlers which enables us to implement process as then chain which is going to be executed in respond with the result of asynchronous result. HTTP requests are always inpredictable, because HTTP request sometimes consumes much more time we expect, and also sometimes not. Promise is always used when we use HTTP request in order to handle the asynchronous response of it with event handlers.
In ES2015 syntax, you can implement functions with async/await syntax to hanle Promise objects as it looks synchronous.

Angular2 post request despite a XMLHttRequest error

I send a request to a remote API. It takes a little time for API to proceed on its side.
After this little waiting time, i can see in network tab a HTTP 200. In the response, I got the proper intended information. Everything on the API side works fine.
BIT on the console, I can see I encountered a XMLHttpRequest Error.
Why, especially if I have a XMLHttpRequest Error, the POST is completed with 200? Shouldn't it be "blocked" by Angular2?
The unintended result is: my file is correctly uploaded and handled by the API, but in Angular2, it triggers the ERROR part of my call.
If I use https://resttesttest.com/ for example, it seems to encounter the same error but it doesn't finalize the POST:
Oh no! Javascript returned an
HTTP 0 error. One common reason this might happen is that you
requested a cross-domain resource from a server that did not include
the appropriate CORS headers in the response.
Angular 2 Code for this call
this.http
.post(this.documentUploadAPIUrl, formData, options)
.subscribe(
res => {
this.responseData = res.json();
console.log(this.responseData);
console.log('Uploaded a blob or file!');
},
error => {
console.log('Upload failed! Error:', error);
}
);
try to set withCredential attribute of xmlHttpRequest to true, this will send credentials managed by the browser, in angular 2 you can do like this
import { RequestOptions } from '#angular/http';
this.http
.post(this.documentUploadAPIUrl, formData, this.post_options)
.subscribe(
res => {
this.responseData = res.json();
console.log(this.responseData);
console.log('Uploaded a blob or file!');
},
error => {
console.log('Upload failed! Error:', error);
}
);
post_options() {
return new RequestOptions({ method: 'post', withCredentials : true });
}

RxJS calling second operation when first is successful

I'm using Angular2 and rxjs.
I have an operation called login(). This will use a http.post request to send the authentication details to the server and will then receive a token back.
It needs to read the result and if the token is received successfully it will do some operations to validate the token and decode it, and if all of this is OK then it will send the username from the token to the server with a http.get and retrieve the user's details.
I would like all of the above to be returned as one Observable, but I'm scratching my head as to how two operations that should occur one after the other should be structured using the RxJS way.
I don't think subscribing to the first operation and then calling the second operation inside the first is the "right" way, because then how do you capture a failure in the first one.
Something like this?
this.http.post('http://localhost/auth/token', creds, {
headers: headers
})
.map(res => res.json())
.do(
// validate token
// decode token
)
.thenDo(
// get user details
this.http.get(url, options)
.map(res => res.json())
.do(
//save user and token in localStorage
)
)
i dont know much about Rxjs do and thenDo function but yes you can do like this
this.http.post('http://localhost/auth/token', creds, {
headers: headers
})
.map(res => {
return [{status: res.status , json: res.json()}]
})
.subscribe(res=>{
if(res[0].status == 200){ // do you action depends on status code you got assuming 200 for OK response
this.validateToken() // Validate your token here in some method named as validateToken
this.decodeToken() // decode token here in this method
this.getUserDetail() //if everything worked fine call your another get request in another method
}
},
err => {
console.log(err, err.status) //catch your error here
})
getUserDetail(){
// make http get request for user detail and saveing into locastroage
}
Using flatMap is a good way to chain operations that each return a new Promise or Observable. Each time we need to map over a function that returns a Promise or Observable, we can use flatMap to construct a stream that emits the resolved data. Here we construct an Observable of user data, and finally we can subscribe to it (to save to localstorage, etc).
I've assumed your validation code is just some function that returns a Promise or Observable.
const options = { headers };
const user$ = this.http.post('http://localhost/auth/token', creds, options)
.map(res => res.json())
.flatMap(validationFunctionThatReturnsAPromise)
.flatMap(authResponse => {
// get user details
return this.http.get(url, options).map(res => res.json());
});
user$.subscribe(user => /** do something with the user data **/);

Resources