user4526623
user4526623

Reputation:

chaining ngResource $promise success and errors

I'd like to know if it's possible to handle the $promise returned by ngResource on multiple levels so that the code is DRY

here is a simple example

aService = function(aResource) {
  var error, success;
  success = function(response) {
    console.log('Service Success');
  };
  error = function(response) {
    console.log('Service Error');
  };

  this.servicePostReq = function() {
    return aResource.save().$promise.then(success, error);
  };
  return this;

angular.module('app.service').factory('aService', ['aResource', aService]);

this works fine so far... it Service Success when response is OK and it Service Error when response is not OK

but when I add a controller that use this aService like following

aController = function(aService) {
  var error, success;
  success = function(response) {
    console.log('Controller Success');
  };
  error = function(response) {
    console.log('Controller Error');
  };
  this.controllerPostReq = function() {
    aService.servicePostReq().then(success, error);
  };

  return this;
};

angular.module('app.controller').controller('aController', ['aService', aController]);

the controller always success...

so if the request return success the output is

Service Success
Controller Success

and if the request fails the output is

Service Error
Controller Success

how do I chain the promise so that I don't have to add the code handled in the service for every controller that use the service ?

Upvotes: 3

Views: 2424

Answers (3)

mz3
mz3

Reputation: 1344

The problem is your service. Change this:

  this.servicePostReq = function() {
    return aResource.save().$promise.then(success, error);
  };

To this:

  this.servicePostReq = function() {
    return aResource.save().$promise.then(success);
  };

Explanation:

Since your service returns aResource.save().$promise.then(success, error), it's returning a new promise with an error handler included. Later, in your controller, you add onto the chain like this.

aService.servicePostReq().then(success, error);

The complete promise chain at this point looks if you expand it out:

return aResource.save().$promise
  .then(successFnFromService, errorFnFromService)
  .then(successFnFromController, errorFnFromController);

Since you catch the error from aResource.save() with errorFnFromService, the promise chain is basically "clean" at this point and it will just continue with the next then.

By removing the first error handler, you allow the error to be caught later on.

A better way (in general) to handle errors in promise chains would be to use a single .catch() at the end of the chain.

Consider this bad code (try running on your browser console):

new Promise(
  function(resolve, reject){
    reject('first');
  }).then(
    function(result) {
      console.log('1st success!', result);
      return result;
    },
    function(err) {
      console.log('1st error!', err);
      return err;
    }
  ).then(
    function(result){
      console.log('2nd success!', result);
    },
    function(err){
      console.log("2nd error!", err);
    }
  );

Output:

1st error! first
2nd success! first

Better way:

new Promise(
  function(resolve, reject){
    reject('first');
  }).then(function(result) {
    console.log('1st success!', result);
    return result;
  }).then(function(result){
    console.log('2nd success!', result);
  // catch error once at the end
  }).catch(function(err){
    console.log("error!", err);
  });

Output:

error! first

Try both of those in browser console, and change reject to resolve to see how it affects the output.

Upvotes: 4

user2943490
user2943490

Reputation: 6940

To properly chain promises, both success and error handlers should return some value. The return values are automatically wrapped in a new promise for you. This means that in the case of errors, you must return a rejected promise using $q.reject.

So your service should look like this:

aService = function($q, aResource) {
  var error, success;
  success = function(response) {
    // important! return a value, handlers down the chain will
    // execute their success handlers and receive this value
    return 'Service Success';
  };
  error = function(response) {
    // important! return a REJECTION, handlers down the chain will
    // execute their error handlers and receive this value
    return $q.reject('Service Error');
  };

  this.servicePostReq = function() {
    return aResource.save().$promise.then(success, error);
  };
  return this;

angular.module('app.service').factory('aService', ['$q', 'aResource', aService]);

Upvotes: 1

a14m
a14m

Reputation: 8055

add a dependency on the $q and use $q.reject to control the execution...

in your example you need a $q.reject in the aService.error method

as mentioned here in the $q docs

reject(reason);

Creates a promise that is resolved as rejected with the specified reason. This api should be used to forward rejection in a chain of promises. If you are dealing with the last promise in a promise chain, you don't need to worry about it.

When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of reject as the throw keyword in JavaScript. This also means that if you "catch" an error via a promise error callback and you want to forward the error to the promise derived from the current promise, you have to "rethrow" the error by returning a rejection constructed via reject.

Upvotes: 1

Related Questions