How to cancel a HTTPRequest in Angular 2? - http

How to cancel a HTTPRequest in Angular 2?
I know how to reject the request promise only.
return new Promise((resolve, reject) => {
this.currentLoading.set(url, {resolve, reject});
this.http.get(url, {headers: reqHeaders})
.subscribe(
(res) => {
res = res.json();
this.currentLoading.delete(url);
this.cache.set(url, res);
resolve(res);
}
);
});

You can use the following simple solution:
if ( this.subscription ) {
this.subscription.unsubscribe();
}
this.subscription = this.http.get( 'awesomeApi' )
.subscribe((res)=> {
// your awesome code..
})

You can call unsubscribe
let sub = this.http.get(url, {headers: reqHeaders})
.subscribe(
(res) => {
res = res.json();
this.currentLoading.delete(url);
this.cache.set(url, res);
resolve(res);
}
);
sub.unsubscribe();
More info here: http://www.syntaxsuccess.com/viewarticle/angular-2.0-and-http

You can use SwitchMap on the observable which will cancel any previous request's responses and only request the latest:
https://www.learnrxjs.io/operators/transformation/switchmap.html

A little late for the party, but here is my take:
import { Injectable } from '#angular/core'
import { Http } from '#angular/http'
import { Observable } from 'rxjs/Observable'
import { Subscriber } from 'rxjs/Subscriber'
#Injectable ()
export class SomeHttpServiceService {
private subscriber: Subscriber<any>
constructor(private http: Http){ }
public cancelableRequest() {
let o = new Observable(obs => subscriber = obs)
return this.http.get('someurl').takeUntil(o)
.toPromise() //I dont like observables
.then(res => {
o.unsubscribe
return res
})
}
public cancelRequest() {
subscriber.error('whatever')
}
}
This allows you to manually cancel a request. I sometimes end up with an observable or promise that will make changes to a result on the page. If the request was initiated automatically (user didn't type anyting in a field for x millis) being able to abort the request is nice (user is suddenly typing something again)...
takeUntil should also work with a simple timeout (Observable.timer) if that is what you are looking for
https://www.learnrxjs.io/learn-rxjs/operators/filtering/takeuntil

Use switchMap [docs], which will cancel all in-flight requests and use only the latest.
get(endpoint: string): Observable<any> {
const headers: Observable<{url: string, headers: HttpHeaders}> = this.getConfig();
return headers.pipe(
switchMap(obj => this.http.get(`${obj.url}${endpoint}`, { headers: obj.headers, params: params }) ),
shareReplay(1)
);
}
shareReplay will emit the latest value for any late subscribers.

This is a great thread, and I have a little more info to provide. I have an API call that could potentially go on for a very long time. So I needed the previous request to cancel with a timeout. I just figured out today that I can add a timeout operator to the pipe function. Once the timeout completes its count, that will cancel the previous HTTP request.
Example...
return this.exampleHttpRequest()
.pipe(
timeout(3000),
catchError(err => console.log(error)
)

Related

Api middleware with redux-observable

I'm refactoring my react/redux app to use redux-observable instead of redux-thunk. Using thunk, I have an api middleware set up to listen for any actions with a CALL_API key and do some manipulation of the data, prepare headers, prepare full url, perform an api call using axios, and also do some additional action dispatches related to an api call.
Importantly, the api middleware dispatches a REQUEST_START action which gives the request an id and sets its status to pending in the network part of my state. When the promise from axios resolves or rejects, the middleware dispatches a REQUEST_END action, updating the state so that the current request is set to resolved or rejected. Then the response is returned to the calling action creator that initially dispatched the CALL_API action.
I have not been able to figure out how to do this with redux-observable. The part about the api middleware described above that I want to replicate is the REQUEST_START and REQUEST_END action dispatches. It's very convenient to have a centralized place where all api call related stuff is handled. I know I can effectively dispatch the REQUEST_START and REQUEST_END actions in each of my epics that does an api call, but I don't want to have to repeat the same code in many places.
I managed to partially solve this by creating an apiCallEpic which listens for actions with type CALL_API and does the above setup for api calls. However, an issue (or rather, something I don't like) is that the epic that initiates the api call (e.g. getCurrentUserEpic) essentially gives up control to apiCallEpic.
So, for example, when the api call succeeds and has a response, I may want to format that response data in some way before dispatching an action to be handled by my reducer. That is, getCurrentUserEpic should do some formatting of data returned from api call before sending to reducer. I was able to achieve something close to this by passing a payloadHandler callback function defined in getCurrentUserEpic that the apiCallEpic can call if/when it gets a successful response. However, I don't like this callback architecture and it seems like there's got to be a better way.
Here is some code that demonstrates my use of api middleware using thunk.
import axios from 'axios';
// actionCreators.js
// action types
const CALL_API = "CALL_API";
const FETCH_CURRENT_USER = "FETCH_CURRENT_USER";
const RECEIVE_CURRENT_USER = "RECEIVE_CURRENT_USER";
// action creators for request start and end
export const reqStart = (params = {}) => (dispatch) => {
const reduxAction = {
type: REQ_START,
status: 'pending',
statusCode: null,
requestId: params.requestId,
}
dispatch(reduxAction);
}
export const reqEnd = (params = {}) => (dispatch) => {
const {
requestId,
response = null,
error = null,
} = params;
let reduxAction = {}
if (response) {
reduxAction = {
type: REQ_END,
status: 'success',
statusCode: response.status,
requestId,
}
}
else if (error) {
if (error.response) {
reduxAction = {
type: REQ_END,
status: 'failed',
statusCode: error.response.status,
requestId,
}
}
else {
reduxAction = {
type: REQ_END,
status: 'failed',
statusCode: 500,
requestId,
}
}
}
dispatch(reduxAction);
}
// some api call to fetch data
export const fetchCurrentUser = (params = {}) => (dispatch) => {
const config = {
url: '/current_user',
method: 'get',
}
const apiCall = {
[CALL_API]: {
config,
requestId: FETCH_CURRENT_USER,
}
}
return dispatch(apiCall)
.then(response => {
dispatch({
type: RECEIVE_CURRENT_USER,
payload: {response},
})
return Promise.resolve({response});
})
.catch(error => {
return Promise.reject({error});
})
}
// apiMiddleware.js
// api endpoint
const API_ENTRY = "https://my-api.com";
// utility functions for request preparation
export const makeFullUrl = (params) => {
// ...prepend endpoint url with API_ENTRY constant
return fullUrl
}
export const makeHeaders = (params) => {
// ...add auth token to headers, etc.
return headers;
}
export default store => next => action => {
const call = action[CALL_API];
if (call === undefined) {
return next(action);
}
const requestId = call.requestId;
store.dispatch(reqStart({requestId}));
const config = {
...call.config,
url: makeFullUrl(call.config),
headers: makeHeaders(call.config);
}
return axios(config)
.then(response => {
store.dispatch(reqEnd({
response,
requestId,
}))
return Promise.resolve(response);
})
.catch(error => {
store.dispatch(reqEnd({
error,
requestId,
}))
return Promise.reject(error);
})
}
// reducers.js
// Not included, but you can imagine reducers handle the
// above defined action types and update the state
// accordingly. Most usefully, components can always
// subscribe to specific api calls and check the request
// status. Showing loading indicators is one
// use case.
Here's the code I've implemented to accomplish a similar thing with redux-observable.
export const fetchCurrentUserEpic = (action$, state$) => {
const requestType = FETCH_CURRENT_USER;
const successType = RECEIVE_CURRENT_USER;
const requestConfig = {
url: "/current_user",
method: "get",
}
const payload = {requestConfig, requestType, successType};
const payloadNormalizer = ({response}) => {
return {currentUser: response.data.data};
}
return action$.ofType(FETCH_CURRENT_USER).pipe(
switchMap((action) => of({
type: CALL_API,
payload: {...payload, requestId: action.requestId, shouldFail: action.shouldFail, payloadNormalizer},
})),
)
}
export const apiEpic = (action$, state$) => {
return action$.ofType(CALL_API).pipe(
mergeMap((action) => (
concat(
of({type: REQ_START, payload: {requestId: action.payload.requestId, requestType: action.payload.requestType}}),
from(callApi(action.payload.requestConfig, action.payload.shouldFail)).pipe(
map(response => {
return {
type: action.payload.successType,
payload: action.payload.payloadNormalizer({response})
}
}),
map(() => {
return {
type: REQ_END,
payload: {status: 'success', requestId: action.payload.requestId, requestType: action.payload.requestType},
}
})
)
)
).pipe(
catchError(error => {
console.log('error', error);
return of({type: REQ_END, payload: {status: 'failed', requestId: action.payload.requestId, requestType: action.payload.requestType}, error});
})
)
)
)
}
Any comments or suggestions are appreciated!
I've found redux-fetch-epic-builder (A lib for building "fetch actions" and generic epics handled by redux-observable) to be similar to what you are trying to achieve here (beware it uses rxjs 5, this guide to rescue). It uses fetch, not axios, but it's easy to replace that. Plus it has transformers for successful/failed actions.
The library is a bit old, but the base idea to overcome boilerplate code is still valid: Generic epic-builder to fetch data with calls to API(s).
I am a novice in React / Redux / RxJS, but the only problem I see with the redux-fetch-epic-builder is the way to configure the client (in axios terms). That is, I am not fully satisfied with (due to it being not FSA or RSAA):
//action creators
const getComments = (id, page = 1) => ({
type: GET_COMMENTS,
host: 'http://myblog.com',
path: `/posts/${id}/comments`,
query: {
page,
},
})
// ...
const epics = [
buildEpic(GET_COMMENTS),
]
but this may still be an elegant way. And the license allow to develop the library further. I have not converted the example from the library documentation to your user-related example, but with react-observable there is certainly no need to introduce a separate "api middleware". (Also, I like /SUBACTION better than _SUBACTION, but it's trivial to change.)

Ionic 2: stop an http request

My provider makes available an API for an http.get request:
join(){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('x-access-token',this.getToken());
return Observable.create(observer =>{
this.http.get('/localhost/chat/'+this.room,{headers : headers})
.map(res => res.json())
.subscribe(
data=>{
observer.next(data);
},
(err) =>{
observer.error(err);
}
);
})
}
My page.ts just use this API cyclically:
join(){
this.myProvider.join().subscribe(
(data)=>{
if(data.success){
... /* doing smt */ ....
this.join();
}else{
this.message=data.message;
//TBD sleep....
//this.join();
}
},
(err) => {
this.message="Connectivity with server Lost...";
});
}
My question is: I would like to write a function in page.ts in order to stop this cycle.
How can I kill a pending get request?
A solution that doesn't work was
I tried to keep a pointer to the observable object in my page.ts:
export class Page {
...
join_channel: any;
join(){
this.join_channel = this.myProvider.join().subscribe(
(data)=>{
...
this.join();
...
Then I by calling the this.join_channel.unsubscribe() I wanted to close the request, so in my case:
ionViewWillLeave() {
this.join_channel.unsubscribe();
delete this;
}
But even by unsubscribing, the get request is still there pending; so when I try to enter again in my page, a new join() can't receive a http.get response at the first step, because the answer will be used before for the previous request which is still pending.
Use timeout from rxjs
this.http.get(API)
.timeout(2000)
.map(res => res.json()).subscribe((data) => {
return data;
},
(err) => {
return err;
}
);
Don't forget to import import 'rxjs/add/operator/timeout';
If you are using angular 6 you have to use
pipe(timeout(2000))
this.http.get(API)
.pipe(timeout(2000))
.map(res => res.json()).subscribe((data) => {
return data;
},
(err) => {
return err;
}
);

Angular 2 how to make a nested observable

I am trying to write up our httpService, it should have a post method that checks to see if a cookie exists with an auth token, if it does then it should append the auth header and make the post request.
However if the cookie doesn't exist I need to load a local json file that contains the token and use it to create the cookie, then append the auth header and make the post request.
The issue I'm having is that if the cookie doesn't exist I need to make a observable wait for another observable. I had thought the solution was to use switchMap, but that doesn't play well with .subscribe which is necessary for the http.post request to initialize.
private makePostRequest(address: string, payload: any, callback: any): Observable<any> {
return this.http.post(address, payload, { headers: this.headers })
.map(callback)
.catch(( error: any ) => this.handleError(error));
}
public post(address: string, payload: any, callback: any): Observable<any> {
if (this.hasOAuth2()) {
this.appendHeader(this.cookieService.get('oauth2'));
return this.makePostRequest(address, payload, callback);
} else if (this.isLocalhost()) {
return this.setAuthCookie()
.switchMap(() => this.makePostRequest(address, payload, callback));
} else {
return this.handleError('Could not locate oauth2 cookie');
}
}
private setAuthCookie(): Observable<any> {
return this.http.get('./json/token.json')
.map((res: Response) => {
let oauth2: any = res.json();
this.cookieService.set('oauth2', oauth2.access_token, oauth2.expiration);
this.appendHeader(oauth2.access_token);
})
.catch((error: any) => {
console.log('No dev token was found', error);
return Observable.throw(error);
});
}
Update: Where this gets weird is that more or less the exact game code works correctly with a get request.
private makeGetRequest(address: string, callback: any): Observable<any> {
return this.http.get(address, { headers: this.headers })
.map(callback)
.catch(( error: any ) => this.handleError(error));
}
public get(address: string, callback: any): Observable<any> {
if (this.hasOAuth2()) {
this.appendHeader(this.cookieService.get('oauth2'));
return this.makeGetRequest(address, callback);
} else if (this.isLocalhost()) {
return this.setAuthCookie()
.switchMap(() => this.makeGetRequest(address, callback));
} else {
return this.handleError('Could not locate oauth2 cookie');
}
}
Solution: I wasn't subscribing to the httpService.post observable so it wasn't ever being initialized.
Add an empty .subscribe() to your second case:
return this.setAuthCookie()
.map(() => { })
.switchMap(() => { // I need to switchMap due to the http.get request in the setAuthCookie method
this.makePostRequest(address, payload, callback).subscribe(); // Again I need this or the post request won't be made
}).subscribe(); // <--- here
It will activate the http call.
I had never subscribed to my httpService.post observable so it was never being initialized. Adding the later subscribe calls was causing it to be initialized incorrectly.

How to set a timeout in Angular 2 when using HTTP + toPromise()?

I found this answer where the solution proposed is to use Observables to set http requests' timeout.
However my code is structured to use mostly promises (I use observables where I want to have data automatically updated - that's not the case of the API calls).
Here is my code (inspired by Angular 2 tutorial):
makePostRequest(requestUrl:string, requestBody: any, requestOptions?: RequestOptions): Promise<any> {
requestOptions = requestOptions || new RequestOptions({ headers: this._defaultHeaders });
return this._http.post(requestUrl, JSON.stringify(requestBody), requestOptions)
.toPromise()
.then(this.extractData)
.catch(this.handleError)
}
How to set a timeout and throw an error (if the timeout expires) that I then catch in .catch() or - alternatively - replicate the exact precise behavior with Observables (including converting the result to a Promise and not monitoring for monitoring for API update(*))?
(*) NOTE: I'm not sure whether Observables keep calling the APIs to check for new data, but that's not the point of my question, I just want to make sure this behavior does not occur.
I would expect this to do what you want (not tried):
makePostRequest(requestUrl:string, requestBody: any, requestOptions?: RequestOptions): Promise<any> {
requestOptions = requestOptions || new RequestOptions({ headers: this._defaultHeaders });
return this._http.post(requestUrl, JSON.stringify(requestBody), requestOptions)
.timeout(3000, new Error('timeout exceeded'))
.toPromise()
.then(this.extractData)
.catch(this.handleError)
}
From Angular2 timeout in http post request
The solution (right chain + imports) I found:
// ! must import these
...
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
return this._http.get(requestUrl, requestOptions)
.timeout(5000, new Error( 'HTTP (GET) timeout for path: ' + requestUrl))
.map(this.extractData)
.toPromise()
.catch(this.handleError);
I approached this a bit differently. I had logic that relied on a promise being returned—and doing .timeout caused it to immediately fail regardless of the timeout duration.
My solution was to create a new promise instead of using toPromise:
const timeoutInMs = 3000;
const request = this._http
.post(/* ... */)
.timeout(timeoutInMs);
return new Promise((resolve, reject) => {
request
.take(1)
.subscribe(
data => resolve(data),
error => reject(error),
);
});
If you use this a lot, you could refactor it into a function (not yet tested):
const toPromiseWithTimeout = <T>(obs: Observable<T>, ms): Promise<T> =>
new Promise<T>((resolve, reject) => {
obs
.timeout(ms)
.take(1)
.subscribe(
data => resolve(data),
error => reject(error),
);
});
And to use it:
const timeoutInMs = 3000;
const request = this._http
.post<ResponseType>(/* ... */);
return toPromiseWithTimeout(request, timeoutInMs);

RxJS and AngularJS HTTP - how can I achieve this?

I'm writing a small utility function that wrap a call to AngularJS http.get with the necessary authentication headers:
get(endpoint: string): Observable {
var headers = new Headers();
this._appendAuthentificationHeaders( headers, this.user.credentials);
return this.http.get(endpoint, { headers: headers })
.map(res => res.json());
}
The point here is that if this.user is null, the method will just crash.
So I have three options:
Return null and check that return value on every call...
Throw an exception
Find a way to also return an RxJS Observable object that will directly trigger the error handler.
I would like to implement the third method, as it would allow me unify this method's behavior: It always returns an observable no matter what happen.
Do you have an idea about how to do that?
Do I have to create a new Observable and kind of merge those two?
What can I do?
If the user is null, you can simply return a raw observable that triggers an error:
if (this.user == null) {
return Observable.create((observer) => {
observer.error('User is null');
});
}
(...)
or leverage the throw operator:
if (this.user == null) {
return Observable.throw('User is null');
}
(...)
This way the second method of the subscribe method will be called:
observable.subscribe(
(data) => {
(...)
},
(err) => {
// Will be called in this case
}
);
I think the cleanest way would be to wrap the whole function body to an observable, as it will turn any accidental error to an observable error. Something like this:
get(endpoint: string): Observable {
return Rx.Observable.defer(() => {
var headers = new Headers();
this._appendAuthentificationHeaders(headers, this.user.credentials);
return Rx.Observable.just(headers);
})
.flatMap(headers => this.http.get(endpoint, { headers: headers }))
.map(res => res.json());
}
However I still do not agree with http.get returning an observable instead of a promise. As these are single valued observables, your function could be a simple async function (sry, js instead of ts):
async get(endpoint) {
var headers = new Headers();
this._appendAuthentificationHeaders(headers, this.user.credentials);
const res = await this.http.get(endpoint, { headers })).toPromise();
return res.json();
}

Resources