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;
}
);
Related
Here's what I have going:
import 'whatwg-fetch';
function fetchVehicle(id) {
return dispatch => {
return dispatch({
type: 'FETCH_VEHICLE',
payload: fetch(`http://swapi.co/api/vehicles/${id}/`)
.then(status)
.then(res => res.json())
.catch(error => {
throw(error);
})
});
};
}
function status(res) {
if (!res.ok) {
return Promise.reject()
}
return res;
}
EDIT: The promise doesn't get rejected, that's what I'm trying to figure out.
I'm using this fetch polyfill in Redux with redux-promise-middleware.
Fetch promises only reject with a TypeError when a network error occurs. Since 4xx and 5xx responses aren't network errors, there's nothing to catch. You'll need to throw an error yourself to use Promise#catch.
A fetch Response conveniently supplies an ok , which tells you whether the request succeeded. Something like this should do the trick:
fetch(url).then((response) => {
if (response.ok) {
return response.json();
}
throw new Error('Something went wrong');
})
.then((responseJson) => {
// Do something with the response
})
.catch((error) => {
console.log(error)
});
The following login with username and password example shows how to:
Check response.ok
reject if not OK, instead of throw an error
Further process any error hints from server, e.g. validation issues
login() {
const url = "https://example.com/api/users/login";
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify({
email: this.username,
password: this.password,
}),
})
.then((response) => {
// 1. check response.ok
if (response.ok) {
return response.json();
}
return Promise.reject(response); // 2. reject instead of throw
})
.then((json) => {
// all good, token is ready
this.store.commit("token", json.access_token);
})
.catch((response) => {
console.log(response.status, response.statusText);
// 3. get error messages, if any
response.json().then((json: any) => {
console.log(json);
})
});
},
Thanks for the help everyone, rejecting the promise in .catch() solved my issue:
export function fetchVehicle(id) {
return dispatch => {
return dispatch({
type: 'FETCH_VEHICLE',
payload: fetch(`http://swapi.co/api/vehicles/${id}/`)
.then(status)
.then(res => res.json())
.catch(error => {
return Promise.reject()
})
});
};
}
function status(res) {
if (!res.ok) {
throw new Error(res.statusText);
}
return res;
}
For me,
fny answers really got it all. since fetch is not throwing error, we need to throw/handle the error ourselves.
Posting my solution with async/await. I think it's more strait forward and readable
Solution 1: Not throwing an error, handle the error ourselves
async _fetch(request) {
const fetchResult = await fetch(request); //Making the req
const result = await fetchResult.json(); // parsing the response
if (fetchResult.ok) {
return result; // return success object
}
const responseError = {
type: 'Error',
message: result.message || 'Something went wrong',
data: result.data || '',
code: result.code || '',
};
const error = new Error();
error.info = responseError;
return (error);
}
Here if we getting an error, we are building an error object, plain JS object and returning it, the con is that we need to handle it outside.
How to use:
const userSaved = await apiCall(data); // calling fetch
if (userSaved instanceof Error) {
debug.log('Failed saving user', userSaved); // handle error
return;
}
debug.log('Success saving user', userSaved); // handle success
Solution 2: Throwing an error, using try/catch
async _fetch(request) {
const fetchResult = await fetch(request);
const result = await fetchResult.json();
if (fetchResult.ok) {
return result;
}
const responseError = {
type: 'Error',
message: result.message || 'Something went wrong',
data: result.data || '',
code: result.code || '',
};
let error = new Error();
error = { ...error, ...responseError };
throw (error);
}
Here we are throwing and error that we created, since Error ctor approve only string, Im creating the plain Error js object, and the use will be:
try {
const userSaved = await apiCall(data); // calling fetch
debug.log('Success saving user', userSaved); // handle success
} catch (e) {
debug.log('Failed saving user', userSaved); // handle error
}
Solution 3: Using customer error
async _fetch(request) {
const fetchResult = await fetch(request);
const result = await fetchResult.json();
if (fetchResult.ok) {
return result;
}
throw new ClassError(result.message, result.data, result.code);
}
And:
class ClassError extends Error {
constructor(message = 'Something went wrong', data = '', code = '') {
super();
this.message = message;
this.data = data;
this.code = code;
}
}
Hope it helped.
2021 TypeScript Answer
What I do is write a fetch wrapper that takes a generic and if the response is ok it will auto .json() and type assert the result, otherwise the wrapper throws the response
export const fetcher = async <T>(input: RequestInfo, init?: RequestInit) => {
const response = await fetch(input, init);
if (!response.ok) {
throw response;
}
return response.json() as Promise<T>;
};
and then I'll catch errors and check if they are an instanceof Response. That way TypeScript knows that error has Response properties such as status statusText body headers etc. and I can apply a custom message for each 4xx 5xx status code.
try {
return await fetcher<LoginResponse>("http://localhost:8080/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "user#example.com", password: "passw0rd" }),
});
} catch (error) {
if (error instanceof Response) {
switch (error.status) {
case 401:
throw new Error("Invalid login credentials");
/* ... */
default:
throw new Error(`Unknown server error occured: ${error.statusText}`);
}
}
throw new Error(`Something went wrong: ${error.message || error}`);
}
and if something like a network error occurs it can be caught outside of the instanceof Response check with a more generic message i.e.
throw new Error(`Something went wrong: ${error.message || error}`);
The answer by #fny (the accepted answer) didn't work for me. The throw new Error() wasn't getting picked up by the .catch. My solution was to wrap the fetch with a function that builds a new promise:
function my_fetch(url, args) {
return new Promise((resolve, reject) => {
fetch(url, args)
.then((response) => {
response.text().then((body) => {
if (response.ok) {
resolve(body)
} else {
reject(body)
}
})
})
.catch((error) => { reject(error) })
})
}
Now every error and non-ok return will be picked up by the .catch method:
my_fetch(url, args)
.then((response) => {
// Do something with the response
})
.catch((error) => {
// Do something with the error
})
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
fetch("https://example.com/api/users")
.then(handleErrors)
.then(response => console.log("ok") )
.catch(error => console.log(error) );
I wasn't satisfied with any of the suggested solutions, so I played a bit with Fetch API to find a way to handle both success responses and error responses.
Plan was to get {status: XXX, message: 'a message'} format as a result in both cases.
Note: Success response can contain an empty body. In that case we fallback and use Response.status and Response.statusText to populate resulting response object.
fetch(url)
.then(handleResponse)
.then((responseJson) => {
// Do something with the response
})
.catch((error) => {
console.log(error)
});
export const handleResponse = (res) => {
if (!res.ok) {
return res
.text()
.then(result => JSON.parse(result))
.then(result => Promise.reject({ status: result.status, message: result.message }));
}
return res
.json()
.then(result => Promise.resolve(result))
.catch(() => Promise.resolve({ status: res.status, message: res.statusText }));
};
I just checked the status of the response object:
$promise.then( function successCallback(response) {
console.log(response);
if (response.status === 200) { ... }
});
Hope this helps for me throw Error is not working
function handleErrors(response) {
if (!response.ok) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject({
status: response.status,
statusText: response.statusText,
});
}, 0);
});
}
return response.json();
}
function clickHandler(event) {
const textInput = input.value;
let output;
fetch(`${URL}${encodeURI(textInput)}`)
.then(handleErrors)
.then((json) => {
output = json.contents.translated;
console.log(output);
outputDiv.innerHTML = "<p>" + output + "</p>";
})
.catch((error) => alert(error.statusText));
}
Another (shorter) version that resonates with most answers:
fetch(url)
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(json => doStuff(json)) //all good
//next line is optional
.catch(response => handleError(response)) //handle error
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.
I'm very bad when it comes to thinking of a title question, sorry for that.
My Problem:
I'm unit testing my async redux actions like it's suggested in the docs. I mock the API calls with nock and check for the dispatched actions with redux-mock-store. It works great so far, but I have one test that fails even though it clearly does work. The dispatched action neither does show up in the array returned by store.getActions() nor is the state changed in store.getState(). I'm sure that it does happen because I can see it when I test manually and observe it with Redux Dev Tools.
The only thing that is different in this action dispatch is that it is called in a promise in a catch of another promise. (I know that sounds confusing, just look at the code!)
What my code looks like:
The action:
export const login = (email, password) => {
return dispatch => {
dispatch(requestSession());
return httpPost(sessionUrl, {
session: {
email,
password
}
})
.then(data => {
dispatch(setUser(data.user));
dispatch(push('/admin'));
})
.catch(error => {
error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
};
}
This httpPost method is just a wrapper around fetch that throws if the status code is not in the 200-299 range and already parses the json to an object if it doesn't fail. I can add it here if it seems relevant, but I don't want to make this longer then it already is.
The action that doesn't show up is dispatch(setError(data.error)).
The test:
it('should create a SET_SESSION_ERROR action', () => {
nock(/example\.com/)
.post(sessionPath, {
session: {
email: fakeUser.email,
password: ''
}
})
.reply(422, {
error: "Invalid email or password"
})
const store = mockStore({
session: {
isFetching: false,
user: null,
error: null
}
});
return store.dispatch(actions.login(
fakeUser.email,
""))
.then(() => {
expect(store.getActions()).toInclude({
type: 'SET_SESSION_ERROR',
error: 'Invalid email or password'
})
})
});
Thanks for even reading.
Edit:
The setErroraction:
const setError = (error) => ({
type: 'SET_SESSION_ERROR',
error,
});
The httpPostmethod:
export const httpPost = (url, data) => (
fetch(url, {
method: 'POST',
headers: createHeaders(),
body: JSON.stringify(data),
})
.then(checkStatus)
.then(response => response.json())
);
const checkStatus = (response) => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
};
Because of you are using nested async function in catch method - you need to return the promise:
.catch(error => {
return error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
Otherwise, dispatch will be called after your assertion.
See primitive examples:
https://jsfiddle.net/d5fynntw/ - Without returning
https://jsfiddle.net/9b1z73xs/ - With returning
I have a http call to a remote server using the angular2/http and thats all working awesomely and such. But I'm having trouble with using the observables that it returns.
When I do my http calls, I'm going through 3 different components.
(for ease here ill just make the calls 3 different methods instead as it works out the same)
What I want to know if its possible to do, is use subscribe twice on the same observable or if there is a way that I can call some functions so I can add logging in the request rather than needing to add it at every location that I make a http call.
request(type: RequestMethod, url: string, data: any) {
let params: URLSearchParams = new URLSearchParams();
let req: RequestOptions;
let headers = new Headers();
for (let key in data) {
params.set(key, data[key]);
}
if (type === 0) {
req = new RequestOptions({
method: type,
search: params
});
} else {
headers.append('Content-Type', 'application/x-www-form-urlencoded');
req = new RequestOptions({
method: type,
body: params.toString(),
headers: headers
});
}
console.log('Http Request: ' + url);
console.log(req);
return this.http.request(this.testUrl + url, req)
.map((res: Response) => res.json());
// .subscribe(
// data => {
// console.log('Data Return for ' + url);
// console.log(data.data);
// // return data; //i removed this subscribe so i can
// }, //pass the map back through and
// err => { //use the subscribe later in the initial call
// this.logError(url, err);
// },
// () => {
// if (afterSuccess) {
// afterSuccess();
// }
// console.log('Completed '+ url);
// }
// );
}
logError(url: string, err: any) {
console.log('Error in call: ' + url);
console.log(err);
}
get(url: string, data: any) {
return this.request(RequestMethod.Get, url, data);
}
post(url: string, data: any) {
return this.request(RequestMethod.Post, url, data);
}
emailExists(email_address: string, user_type?: string) {
let data: any = {};
data.email_address = email_address;
data.service_provider_id = this.service_provider_id;
if (user_type) {
data.user_type = user_type;
}
return this.get('emails/email-address/exists', data);
}
ngOnInit() {
this.emailExists('an.email#gmail.com').subscribe(data => {
this.email = data.data;
console.log('in Signin');
console.log(this.email);
}, err => {
console.log(err);
}, () => {
});
}
In the request method I want to be able to call a console.log to print out the url that was used and print the data received from the server or any errors.
I know I could add it to the success and error portions of the subscribe in the emailExists() call in ngOnInit() but that would mean I would need to put those in every single call throughout the app.
Thanks in advance for your help and time.
You could leverage the do, catch and finally operators of observables to do that.
Here is a sample:
return this.http.request(this.tpayUrl + url, req)
.map((res: Response) => res.json())
.do(data => {
console.log('Data Return for ' + url);
console.log(data.data);
})
.catch(err => {
this.logError(url, err);
return Observable.throw(err);
})
.finally(() => {
if (afterSuccess) {
afterSuccess();
}
console.log('Completed '+ url);
});
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)
)