I'm trying to use the AngularFire library in an Angular application. Some of the AngularFire calls return promises, and I'd like to handle them as observables instead for consistency throughout the app. I'm using rxjs v6
Using from() works well and gives the expected behaviour except when errors occur.
If the promise throws an exception, the observable doesn't seem to see it and a stack trace gets dumped in the console saying Error: Uncaught (in promise).
My first attempt
The AngularFire call that returns the promise:
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete());
}
The calling code:
deleteCampaign(id: string) {
return this.dataStorageService.deleteCampaign(id)
.pipe(
catchError(
err => {
console.log('error when deleting campaign');
console.log(err);
return throwError(err);
}
)
);
}
In this instance, I get the stack trace in the console and the catchError never fires.
My second attempt
I added a catch to the promise inside the from, and then tried rethrowing the error as an observable so it looked like this:
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete().catch(
err => {
throwError(err);
}
));
}
My third attempt
Much like the second attempt, but I tried throwing a plain javascript error. This resulted in the same stack trace however, and it wasn't picked up by the observable either.
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete().catch(
err => {
throw(err);
}
));
}
This stopped the stack trace happening, as now the promise was catching it, but the calling code still never sees the error.
Am I going about this the wrong way? I assumed that by using from() all of the error handling could occur in the observable, and I could leave the promise alone.
I need to be able to either:
1. Have no error handling code where the promise is returned and let the observable take care of it.
1. Have the promise catch block able to rethrow an error thats caught by the observable.
Here's the solution arrived at:
From the front end component, handle the passed error using the error callback in subscribe
onDelete(id: string) {
this.loadingCampaigns = true;
this.campaignService.deleteCampaign(id).subscribe(
_ => {},
err => {
console.log('error detection from the component');
}
);
}
From the campaign service, tap() the error so it can be logged or otherwise:
deleteCampaign(id: string) {
return this.dataStorageService.deleteCampaign(id)
.pipe(
tap(null, () => {console.log('tapped the error');} ),
);
}
Finally, from the data storage component do nothing at all:
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete());
}
You can attach an error callback to Observable.subscribe().
Rx.Observable.from(Promise.reject('Boo!'))
.subscribe(val => {
console.log('success');
},
err => {
console.log(err);
});
// Boo!
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete()).pipe(catchError(err=>{
return throwError(err);
}))
}
deleteCampaign(myid).susbcribe(res=>{
console.log(res);
},error=>{
console.log(error)
})
I put an example using ng-bootstrap modal -that return a promise when open the modal- to convert to a Observable in this stackblitz
Related
component which is calling submitUser
this.someservice.submitUser(postData).subscribe((data) => {
this.viewUsers();
}, (err) => {
console.log('error in the component', err);
});
Here is the service file with submitUser function
public submitUser(reqBody ) {
return this.httpService.post('roles', reqBody, '/business/create')
.pipe(
catchError(
this.httpService.handleError())
);
}
and here is the httpService Post and handleError methods
public post<JSON>(url: string, body: any, param?: string, options?: IRequestOptions): Observable<JSON> {
return this.intercept(this.http.post<JSON>(this.getURLFromMethodName(url, param), body, this.requestOptions(options)));
}
handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error('error from httpclient', error); // log to console instead
throw throwError(new Error(error));
};
}
handleError adisplays the console error, I am trying to return/capture this error in my submitUser function in service.ts
How do i do that ? Any Inputs appreciated, Thanks
Your handleError() method returns an error observable along with logging the error to the console.
When some error occurs, the catchError operator takes that error and gives it to handleError() which in turn returns an error observable.
CASE 1: Returning the error
If you need to pass this error on to the subscriber, you don't have to do anything. The catchError operator is already taking care of it for you.
With the same code, let's say some component is consuming your service, then you can just write
someService.submitUser().subscribe((res) => {
\\ handle success
}, (err) => {
console.error(err); // Will print the error
});
Whenever the error occurs, the catchError is going to return the error observable back to its subscriber and it will go in the error function of the observer as shown in the code snippet above.
CASE 2: Handling the error
The catchError operator accepts a function that takes error as an argument. If you return another observable inside this instead of throwing an error, the subscriber won't get to know that the error had occurred, the success function of the observer will execute.
// Inside the service
public submitUser(reqBody ) {
return this.httpService.post('roles', reqBody, '/business/create')
.pipe(
catchError((err) => of([1,2,3]));
}
// Inside the component consuming the service
someService.submitUser().subscribe((res) => {
console.log(res) // Will print [1,2,3]
}, (err) => {
\\ handle error
});
I just want to ask how should I pass the resolve promise to catch if the value on the resolve is not intended.
e.g.
let prom = getPromise();
prom.then(value => {
if (value.notIWant) {
// Send to catch <-- my question is here, I want to pass it on the catch.
}
// Process data.
}).catch(err => {
// Pass the error through ipc using json, for logging.
});
I tried to using throw but the object cant be parsed to json and just got an empty object.
ANSWER:
#BohdanKhodakivskyi first comment below is the answer I want.
#31py answer is also correct but the #BohdanKhodakivskyi solution is much simplier and will render the same result.
Simply use throw value;. In your case:
prom.then(value => {
if (value.notIWant) {
// Send to catch
throw value;
}
// Process data.
}).catch(err => {
// Pass the error through ipc using json, for logging.
});
Please also note the difference and limitations between using Promise.reject() and throw which is perfectly described in this question. For example, throw will not work in some async scenarios.
You can simply return a rejected promise:
prom.then(value => {
if (value.notIWant) {
return Promise.reject('your custom error or object');
}
// Process data.
}).catch(err => {
console.log(err); // prints 'your custom error or object'
});
.catch actually handles any promise rejection in the chain, so if you're returning a rejected promise, the control automatically flows to catch.
why you just not rethrow the error? throw new Error("something");
You can use outside functions to do it:
var processData = function(data) {
// process data here
}
var logIt = function(data) {
// do logging here..
}
let prom = getPromise();
prom.then(value => {
if (value.notIWant) {
// Send to catch <-- my question is here, I want to pass it on the catch.
logIt(/*pass any thing*/);
}
// Process data.
processData(data);
}).catch(err => {
logIt(/*pass any thing*/);
});
Currently, I'm using fetch with redux-thunk to read code from an API -
my code reads like this:
export function getUsers() {
return (dispatch) => {
// I have some helper code that automatically resolves the json promise
return fetch(`/users`)
.then((resp, json) => {
if (resp.status === 200) {
dispatch(getUsersSuccess(json));
} else {
dispatch(getUsersFail(json));
}
}).catch((err) => {
// network error
dispatch(getUsersFail(err));
});
};
}
The problem here is the catch method, as it will catch any error thrown in the then block. This commonly means that if some React component's render function fails with a programmer error, that error gets swallowed up back into dispatch(getUsersFail(err)).
Ideally, I'd like to detect if err is a fetch error (and dispatch my own action), otherwise throw. However, fetch throws a generic TypeError. How can I reliably detect that the error caught was one thrown by fetch?
Don't use .catch() but install the error handler directly on the fetch promise, as the second - onreject - argument to .then():
return fetch(`/users`)
.then(([resp, json]) => {
if (resp.status === 200) {
dispatch(getUsersSuccess(json));
} else {
dispatch(getUsersFail(json));
}
}, (err) => {
// network error
dispatch(getUsersFail(err));
});
Check out the difference between .then(…).catch(…) and .then(…, …) for details.
Btw, I'd recommend to write
return fetch(`/users`)
.then(([resp, json]) => resp.status === 200 ? getUsersSuccess(json) : getUsersFail(json)
, getUsersFail)
.then(dispatch);
I am using the jsonapi-serializer library to deserialize API data. I promisified the callback using angular's $q constructor and wrapped that in a service, this works fine on the browser, but when I test it using jasmine on the karma runner, the promise doesn't resolve. This is the method on the service (Notice I'm using TypeScript)
public deserialize(type: string, data: any): any {
// get the predefined options for resource type
let deserializeOpts: any = this.deserializeOpts[type];
// use jsonapi-serializer
// the options are not working
let deserializer: any = new JAS.Deserializer({});
console.log(data);
// return a promise with the parsed object
return this._$q((resolve: any, reject: any) => {
deserializer.deserialize(data, (err: any, result: any) => {
if (result) {
console.log(resolve);
resolve(result);
} else {
console.log(err);
reject(err);
}
});
});
}
This is my test after a while trying to debug it
it('should flatten jsonapi user', function (done) {
var deserialized;
JsonapiParser.deserialize(type, apiUser).then(
(result) => {
deserialized = result;
expect(deserialized).toEqual(apiUser);
done();
}
);
});
An this is a sample use of the mentioned deserializer service
// returns the promise so controller can display the errors
return this.$http.get(url)
.then(
(response: any) => {
if (response.data.data.length !== 0) {// deserialize data
return this._deserializer.deserialize('activities', response.data) // the deserializer service is called;
} else { // throw an error if data is empty
return this.$q.reject({ error: this.ACTIVITY.empty });
}
},
() => {
return this.$q.reject({ error: this.ACTIVITY.connectionError });
}
).then(
(deserialized: any) => { // data is copied to original list so it doesn't lose it's bindings
angular.copy(deserialized, this.list); // the result from the deserializer is used
console.log(deserialized);
return this.list;
});
This last block of code works fine when compiled and run on the browser. But the tests get timed out. If I log inside the deserialize method, I can see that the callback gets resolved, but the promise never seems to digest. If I place a $rootScope.$digest() after the call to resolve, the test works, but I don't want to hardcode that in there, especially since the code is working when deployed.
You are close with $rootScope.$digest(), however instead of triggering the digest from the app code, trigger it from the test with $rootScope.$apply().
See Testing Promises and Service Testing.
this works fine on the browser
I can't believe that. You are never calling resolve! Instead of
console.log(resolve);
you need to use
console.log(result);
resolve(result);
Btw, the typical node-style callback promisification is using if (err), not if (result). Maybe you want if (err || !result).
I have the following TypeScript method which returns a promise:
public loadSavedLogin(): ng.IPromise<MyApp.Models.User> {
return this._myAppService.getUser(this.savedUserId).then((result: MyApp.Models.User) => {
if (result) {
this.userId = result.UserID;
this.userName = result.UserName;
}
return result;
}, (error) => {
this._isAuthError = true;
return error;
}
);
}
The problem I have is in the promise's error callback. The upstream calls to this method also rely on a promise so if the error does not bubble up correctly, the upstream promise doesn't function correctly. I found a hackish solution:
(error) => {
try {
this._isAuthError = true;
return error;
} catch (e) {
//If any error occurred above make sure to still throw
throw error;
} finally {
//Allow upstream promises to continue as expected
throw error;
}
}
This works but looks, feels, and is probably all wrong. I feel like I'm missing a proper implementation when handling and bubbling errors in a promise. There has to be a more proper/correct way of handling the error function in this promise as I've done, and yet still allow upstream promise's making a call to this method to work properly as well when handling their own error function.
How do I get the error to bubble without the series of hackish throw statements?
Note: It seems redundant to return the error and throw as well, but the IPromise interface I'm using will not compile if I don't return a value. This is why I return and throw the error.
Note: I read a ton of the questions on handling errors with promises, but none of them are answering the question as I'm asking in regards to preventing the hackish approach I've taken.
I am not conversant with TypeScript, but here is a javascript solution, where you use $q.reject
.then(function() {},
function(error) {
this._isAuthError = true;
return $q.reject(error);
});`
Just throw the error instead of ever returning it:
public loadSavedLogin(): ng.IPromise<MyApp.Models.User> {
return this._myAppService.getUser(this.savedUserId).then((result: MyApp.Models.User) => {
if (result) {
this.userId = result.UserID;
this.userName = result.UserName;
}
return result;
}, (error) => {
this._isAuthError = true;
throw error;
}
);
}
Note that a valid return (i.e. not returning a promise / rejected promise) from a rejection handler makes the next promise in the chain fulfilled.