Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 363
Ben Avatar asked Mar 05 '18 22:03

Ben


People also ask

How do I get data from firestore to Doc?

Reading data from Firestore There are two ways for retrieving data, which is stored in Cloud Firestore. Calling a method to get the data. Setting a listener for receiving data changes events. We send an initial snapshot of the data, and then another snapshot is sent when the document changes.

How do I handle firebase errors?

Stay organized with collections Save and categorize content based on your preferences. The Firebase Authentication SDKs provide a simple way for catching the various errors which may occur which using authentication methods. The SDKs for Flutter expose these errors via the FirebaseAuthException class.

How do I get latest documents on firestore?

Is there any way to get the last created document in Firebase Firestore collection? Yes, there is! The simplest way to achieve this is to add a date property to each object in your collection, then simply query it according to this new property descending and call limit(1) function. That's it!


1 Answers

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.
like image 67
Roamer-1888 Avatar answered Sep 23 '22 04:09

Roamer-1888