Reputation: 16659
In Angular 5 with FireStore and angularfire2, what is the correct way to handle errors when getting a document from a service via a controller?
Service:
getInviteById( inviteId: string ): Promise<any> {
// get requested invite from firestore
var inviteDocument = this.afs.collection( 'invites' ).doc( inviteId );
let invite = inviteDocument.ref.get().then( doc => {
// if document exists
if (doc.exists) {
// return id and data
const id = doc.id;
var data = doc.data() as any;
return { id, ...data };
// if document does not exist
} else {
console.log("Error: No such document!");
// WHAT DO I NEED TO RETURN HERE???
}
// if other error
}).catch(function(error) {
console.log("Error: Getting document:", error);
// WHAT DO I NEED TO RETURN HERE???
});
// return invite
return invite;
};
Controller:
this.inviteService.getInviteById( inviteId )
.then( resolve => {
this.invite = resolve;
})
.catch( err => {
// THIS NEVER GETS CALLED !
console.log("INVITE-COMPONENT: Cannot get invite for this id." );
});
All works well IF a document with the invite-id exists in FireStore. However, if there is no document for the invite id in FireStore, then the service will log "Error: No such document!" (as expected), BUT the component will not go into its own catch
case.
How can I process the "no such document" error in my component, so that I can modify my UI accordingly?
Upvotes: 8
Views: 18843
Reputation: 19288
You can return a rejected promise but it's simpler to throw
.
So, straightforwardly, you might write :
// (1) ILLUSTRATIVE - NOT YET THE FULL SOLUTION
getInviteById(inviteId: string): Promise<any> {
var inviteDocument = this.afs.collection('invites').doc(inviteId);
return inviteDocument.ref.get()
.then(doc => {
if (doc.exists) { // if document exists ...
const id = doc.id;
var data = doc.data() as any;
return {id, ...data}; // ... return id and data.
} else { // if document does not exist ...
throw new Error('No such document!'); // ... throw an Error.
}
})
.catch(error => {
throw new Error('Error: Getting document:'); // throw an Error
});
};
HOWEVER, the inner throw
would be immediately caught by the outer .catch()
and the 'No such document!' error message would be lost in favour of 'Error: Getting document:'.
That loss can be avoided by adjusting the overall pattern as follows:
// (2) ILLUSTRATIVE - NOT YET THE FULL SOLUTION
getInviteById(inviteId: string): Promise<any> {
var inviteDocument = this.afs.collection('invites').doc(inviteId);
return inviteDocument.ref.get()
.catch(error => { // .catch() error arising from inviteDocument.ref.get()
throw new Error('Error: Getting document:');
})
.then(doc => {
if (doc.exists) {
const id = doc.id;
var data = doc.data() as any;
return {id, ...data};
} else {
throw new Error('No such document!'); // can now be caught only by getInviteById's caller
}
});
};
HOWEVER, even that isn't yet correct because the possibilities exist that :
this.afs.collection('invites').doc(inviteId)
might return null
, in which case an error should be thrown.this.afs.collection('invites').doc(inviteId)
or inviteDocument.ref.get()
might throw synchronously.In either case, the caller has a right to expect a promise-returning function always to throw asynchronously regardless of how/where the error arose.
That artifact can be overcome by ensuring var inviteDocument = this.afs.collection('invites').doc(inviteId);
and inviteDocument.ref.get()
are executed from inside the promise chain and the null
case is handled appropriately, as follows :
// (3) SOLUTION
getInviteById(inviteId: string): Promise<any> {
return Promise.resolve() // neutral starter promise
.then(() => {
var inviteDocument = this.afs.collection('invites').doc(inviteId); // now executed inside promise chain
if(inviteDocument) {
return inviteDocument.ref.get(); // now executed inside promise chain.
} else {
throw new Error(); // no point setting an error message here as it will be overridden below.
}
})
.catch(error => {
throw new Error('Error: Getting document:');
})
.then(doc => {
if (doc.exists) {
const id = doc.id;
var data = doc.data() as any;
return {id, ...data};
} else {
throw new Error('No such document!');
}
});
};
The caller (your controller) will catch and log any error arising from getInviteById()
:
this.inviteService.getInviteById(inviteId)
.then(result => { // better not to name the variable `resolve`
this.invite = result;
})
.catch(err => {
console.log("INVITE-COMPONENT: Cannot get invite for this id: " + error.message);
});
Notes
console.log()
is unnecessary inside getInviteById()
(except possibly while debugging). The caller's .catch()
will do all the necessary logging.Upvotes: 5