简体   繁体   中英

Error handling when getting document from Firestore

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?

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:

  1. this.afs.collection('invites').doc(inviteId) might return null , in which case an error should be thrown.
  2. 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

  1. console.log() is unnecessary inside getInviteById() (except possibly while debugging). The caller's .catch() will do all the necessary logging.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM