Rafff
Rafff

Reputation: 1518

How to handle chained promises

I'm a little bit confused how should I handle the promises in my situation.

This is my factory:

return {
  getCategory: function(categoryId) {
    var ref = firebase.database().ref().child('categories').child(categoryId);
    var category = $firebaseObject(ref);
    return category.$loaded().then(function() {
        return category;
    });
  },
  getEntry: function(categoryId, entryId) {
    var ref = firebase.database().ref().child('entries').child(categoryId).child(entryId);
    var entry = $firebaseObject(ref);
    return entry.$loaded().then(function() {
        return entry;
    });
  }
}

In my factory I try to avoid doing like this:

var d = $q.defer();
if() {
    d.resolve();
}
return d.promise;

Because the $loaded() returns a promise itself.

And this is my controller:

var categoryId = 'abc';
var entryId = 'def';
// so here i'm getting the category
MyFactory.getCategory(categoryId)
.then(function(category) {
    if(category.$value === null)
    {
        $scope.error = 'The category does not exist';
    }
    else if(new Date() > new Date(category.expireDate))
    {
        $scope.error = 'The category has expired';
    }
    else {
        $scope.category = category;
        // if no errors
        return MyFactory.getEntry(category.$id, entryId);
    }
})
.then(function(entry) {
    if(entry.$value === null)
    {
        $scope.error = 'No such entry';
    }
    else {
        $scope.entry = entry;
    }
})
.catch(function(error) {
    console.error(error);
});

What I want to achieve is to get the category first, and then whether there are some errors or not, get the entry respectively. The data is coming from a Firebase database.

This is kind of working, however I'm not really sure how should I handle the promise when I want to do a next .then and don't nest them one in the other like this:

MyFactory.getCategory().then(function(category) {
    if(no category errors) {
        MyFactory.getEntry().then(function() {
            // ...
        });
    }
});

For now I'm getting an error in the console (it's type error entry undefined) when for example the category expired or does not exist. I think I did something wrong in the controller when I return but I'm not really sure and hope you can help me dispel all doubts.

So the real question is how should I handle this correctly, to work as expected? Thanks.

Upvotes: 1

Views: 251

Answers (1)

Vladimir Zdenek
Vladimir Zdenek

Reputation: 2290

You should return a rejected promise when there is an error.

Look at the following example:

MyFactory
    .getCategory(categoryId)
    .then(function (category) {
        if (category.$value === null) {
            return $q.reject('The category does not exist');
        }
        if (new Date() > new Date(category.expireDate)) {
            return $q.reject('The category has expired');
        }
        $scope.category = category;
        return MyFactory.getEntry(category.$id, entryId);
    })
    .then(function (entry) {
        if (entry.$value === null) {
            return $q.reject('No such entry');
        }
        $scope.entry = entry;
    })
    .catch(function (error) {
        $scope.error = error;
    });

Do not forget to inject $q to your controller.

Edit

I would also suggest you move the "error logic" to your service, so the controller would always received either data in .then(data => { ... }) or an error string in .catch(error => { ... }). This would make your controllers cleaner and if you use those service method in a different controller, you would not have to replicate your logic there as well.

Service

return {
    getCategory: function(categoryId) {
        var ref = firebase.database().ref().child('categories').child(categoryId);
        var category = $firebaseObject(ref);
        return category.$loaded().then(function() {
            if (category.$value === null) {
                return $q.reject('The category does not exist');
            }
            if (new Date() > new Date(category.expireDate)) {
                return $q.reject('The category has expired');
            }
            return category;
        });
    },
    getEntry: function(categoryId, entryId) {
        var ref = firebase.database().ref().child('entries').child(categoryId).child(entryId);
        var entry = $firebaseObject(ref);
        return entry.$loaded().then(function() {
            if (entry.$value === null) {
                return $q.reject('No such entry');
            }
            return entry;
        });
    }
}

Controller

MyFactory
    .getCategory(categoryId)
    .then(function (category) {
        $scope.category = category;
        return MyFactory.getEntry(category.$id, entryId);
    })
    .then(function (entry) {
        $scope.entry = entry;
    })
    .catch(function (error) {
        $scope.error = error;
    });

Upvotes: 1

Related Questions