In my Ionic2 app, I have a service which handles all http requests.
I have an alert controller when any error occurs in http call. On button click in this alert I want to run that call again. I am able to do it right now. The problem is response is not resolved to page from which function was called.
Code in service:
loadCity(){
return new Promise(resolve => {
this.http.get(url).map(res=>res.json())
.subscribe(data => {resolve(data)},
err => { this.showAlert(err); }
});
}
showAlert(err: any){
// code for alert controller, I am only writing handler of alert
//controller refresh button
handler => {this.loadCity();}
}
Code in CityPage
showCity(){
this.cityService.loadCity()
.then(data => {//process data});
}
Handler is calling function again but this time promise is not resolved to CityPage showCity() function.
When an error occurs in the http request, the error callback function is being called, but you are neither resolving nor rejecting the promise.
You can do something like
loadCity(){
return new Promise( (resolve, reject) => {
this.http.get(url).map(res=>res.json())
.subscribe(
data => {resolve(data)},
err => {
this.showAlert(err);
reject(err);
}
});
}
}
and in the caller
showCity(){
this.cityService.loadCity()
.then( data => {
//process data
})
.catch( error => {
//some error here
})
}
You can see better examples in the docs.
Related
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;
}
);
I'm trying to make a http request and after that return the response object or bool. Doesn't realy matter which one. But i'm can't catch the error. I have a handleError function, but it does not realy work.
My code now looks like this.
The service
updateProduct(product: Product): Promise<number> {
return this.http.put('/api/products/1' + product.id,product)
.toPromise()
.then(response => response.status)
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
//console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
The save function
onSave(): void {
this.productService.updateProduct(this.product)
.then(() => this.goBack())
.catch(er => console.log(er));
}
How can I get this to work?
What i see you're returning error.message when it exists
return Promise.reject(error.message || error);
Return whole error object instead of just message if you want to manipulate with that.
return Promise.reject(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 am doing the following code and unable to figure out that why the data I am obtaining through AJAX is not being assigned to the class variable which is this.users
Code Snippet
getUsers() {
this.http.get('/app/actions.php?method=users')
.map((res:Response) => res.json())
.subscribe(
res => { this.users = res}, // If I console 'res' here it prints as expected
err => console.error(err),
() => console.log('done')
);
console.log(this.users) // Printing 'undefined'
return this.users;
}
Any help will be much appreciated. This (http://prntscr.com/cal2l1) is link to my console output.
It is an asynchronous call, so you don't fetch data right away. However, if you setTimeout() on console.log(), it will be printed correctly because printing will occur after the data is fetched:
getUsers() {
this.http.get('/app/actions.php?method=users')
.map((res:Response) => res.json())
.subscribe(
res => { this.users = res}, // If I console 'res' here it prints as expected
err => console.error(err),
() => console.log('done')
);
setTimeout(() => {
console.log(this.users) // Printing 'undefined'
}, 1000);
return this.users;
}
Reason for Problem
Well, it was really a silly mistake which I was making here. Since, getUsers() was being called after the DOM was loaded so it was assigning the value to class variable which is this.users after loading of DOM which restricted my page to load the required values at page loading stage (not after page loading).
Solution
Angular2 comes with a hook called OnInit or ngOnInit(). I was supposed to call the function in this event as follows.
getUsers() {
this.http.get('/app/actions.php?method=users')
.map((res:Response) => res.json())
.subscribe(
res => { this.users = res},
err => console.error(err),
() => console.log('done')
);
console.log(this.users)
return this.users;
}
ngOnInit() {
getUsers();
}
Documentaion of OnInit: https://angular.io/docs/ts/latest/api/core/index/OnInit-class.html
Also the following documentation came up as a helping tool:
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
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