3

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 ?

3 Answers3

4

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.

mz3
  • 1,314
  • 11
  • 27
1

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.

Community
  • 1
  • 1
a14m
  • 7,808
  • 8
  • 50
  • 67
1

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]);
user2943490
  • 6,900
  • 2
  • 22
  • 38
  • While I agree with most of your answer, I'm confused as to why the aService.success method does not use $q.resolve() but aService.error uses $q.reject(). – coblr Jun 30 '15 at 23:42
  • 1
    This still won't work. The reason is because `aResource.save().$promise.then(success, error)`, is both thenable and contains an error handler. Any .then statements chained onto there will run regardless of if there is an error or not. Example in [this Plunkr](http://plnkr.co/edit/S0UJvE2CJlWcqlLqs23X?p=preview) (open console to see output). – mz3 Jul 13 '15 at 17:04