0

Found solution

Promises : go to next error function Just need to rethrow the error.

Why promise's reference doesn't execute then's error function when spying on it?

Having two buttons returning (calling) same promise, one executes ok ($scope.expected() - button renders red), other goes in to success function ($scope.notExpected() - button renders green) and it is not expected behavour.

Current solution is, to wrap in another promise;

var app = angular.module('thenApp', []);

function ThenCtrl($scope, thenService, $q) {
  $scope.expected = function() {
    return thenService.doIt()
      .then(function() {
        console.log('was a success');
      });
  };

  $scope.notExpected = function() {
    return thenService.doIt()
      .then(function() {
        console.log('was a success');
      }, function() {
        console.log('has failed');
      });
  };

  $scope.solution = function() {
    var deferred = $q.defer();
    thenService.doIt()
      .then(function() {
        deferred.resolve();
        console.log('was a success');
      }, function() {
        deferred.reject();
        console.log('has failed');
      });
    return deferred.promise;
  };
}

app.controller('ThenCtrl', ThenCtrl);

var CLICK_EVENT = 'click';
var TIMEOUT_TO_END_ANIMATION = 1000;
var SUCCESS_CLASS = 'btn-success success';
var FAIL_CLASS = 'btn-error error';
var LOADING_CLASS = 'loading';

/**
 * Inspiration from https://github.com/johannesjo/angular-promise-buttons
 * @param $parse
 * @param $timeout
 * @returns {{scope: {promise: string, stateRedirect: string}, link: link}}
 */
function btnLoader($parse, $timeout) {
  return {
    restrict: 'A',
    require: '?ngClick',
    scope: true,
    link: function(scope, el, attrs) {

      el.addClass('btn-load');

      var promiseWatcher;


      // we need to use evalAsync here, as
      // otherwise the click or submit event
      // won't be ready to be replaced
      scope.$evalAsync(function() {

        var cb = $parse(attrs.ngClick);

        function buttonLoader() {
          // Make sure we run the $digest cycle
          scope.$apply(function() {
            var promise = cb(scope.$parent, {
              $event: CLICK_EVENT
            });

            // only init watcher if not done before
            if (!promiseWatcher) {
              // watch promise to resolve or fail
              promiseWatcher = scope.$watch(function() {
                return promise;
              }, function(nVal) {
                // for regular promises
                if (nVal && nVal.then) {

                  el.unbind(CLICK_EVENT);

                  el.addClass(LOADING_CLASS);
                  el.removeClass(SUCCESS_CLASS);
                  el.removeClass(FAIL_CLASS);

                  nVal.then(function() {
                    // promise was a success
                    el.addClass(SUCCESS_CLASS);

                    if (attrs.alwaysBind) {
                      el.bind(CLICK_EVENT, buttonLoader);
                    }

                  }, function() {
                    // promise was a fail
                    el.addClass(FAIL_CLASS);
                    el.bind(CLICK_EVENT, buttonLoader);
                  }).finally(function() {
                    el.removeClass(LOADING_CLASS);
                    promiseWatcher();
                    promiseWatcher = null;

                    $timeout(function() {
                      el.removeClass(SUCCESS_CLASS);
                      el.removeClass(FAIL_CLASS);
                    }, TIMEOUT_TO_END_ANIMATION);

                  });
                }
              });
            }
          });
        }

        // unbind original click event
        el.unbind(CLICK_EVENT);

        // rebind, but this time watching it's return value
        el.bind(CLICK_EVENT, buttonLoader);

      });
    }
  };
}

app.directive('btnLoader', btnLoader);

function thenService($q, $timeout) {
  thenService.doIt = function() {
    var deferred = $q.defer();
    $timeout(function() {
      deferred.reject(null);
    }, 500);
    return deferred.promise;
  };

  return thenService;
}

app.factory('thenService', thenService);
.loading {
  background: yellow !important;
}
.btn-error {
  background: red !important;
}
.btn-success {
  background: green !important;
}
.btn {
  margin-bottom: 20px;
  border: 1px solid rgba(0, 0, 0, 0.3);
  background: white;
  width: 250px;
  padding: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-transform: uppercase;
}
.btn:hover {
  background: lightgray;
  cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="thenApp" ng-controller="ThenCtrl">
  <p>
    Button action does not spy on then's error function.
  </p>
  <div class="btn" btn-loader="" always-bind="true" ng-click="expected()">
    As expected
  </div>

  <p>
    Button action spies on then's error function.
  </p>
  <div class="btn" btn-loader="" always-bind="true" ng-click="notExpected()">
    Not expected
  </div>

  <p>
    Solution for spying on then's error function.
  </p>
  <div class="btn" btn-loader="" always-bind="true" ng-click="solution()">
    Solution
  </div>
</div>

Community
  • 1
  • 1
terafor
  • 1,620
  • 21
  • 28
  • Error needs to be re thrown. http://stackoverflow.com/questions/34001557/promises-go-to-next-error-function?rq=1 – terafor Aug 17 '16 at 11:45

1 Answers1

0

Error needs to be re thrown.

someService.call().then(function(){
  // success
}, function(e){
  // error
  throw e;
})

Promises : go to next error function

Community
  • 1
  • 1
terafor
  • 1,620
  • 21
  • 28