How to stop/break in the middle of chained promises
Asked Answered
P

2

4

I have a chain of $http calls to server. If one call fails I want to display notification to user and stop the chain. At first I thought I can use the $q.reject to stop the chain, but it turned out the program flow continues to the next then's error handler. I have also tried returning nothing, but the flow still continues.

Can I stop the flow mid chain? So for example the script below should print result: |A|D|F instead of result: |A|D|F|E|H|J.

If the flow cannot be stopped mid chain, must I add extra condition in each then's error handler, or is there more elegant way?

angular.module("MyModule", []).controller("MyCtrl", ["$scope", "$q", "$timeout",
    function($scope, $q, $timeout) {
        $scope.result = "";
        var d0 = $q.defer();
        $timeout(function() {
            d0.reject("A");       // the promise will fail
        }, 1000);
        d0.promise.then(
            function(response) {
                $scope.result += "|" + response + "|B";
                var d1 = $q.defer();
                $timeout(function() {
                    d1.resolve("C");
                }, 1000);
                return d1.promise;
            },
            function(response) {
                $scope.result += "|" + response + "|D";
                return $q.reject("E");
            }
        ).finally(                // it should stop here ...
            function() { $scope.result += "|F"; }
        ).then(
            function(response) {
                $scope.result += "|" + response + "|G";
            },
            function(response) {  // ... but instead it continues here
                $scope.result += "|" + response + "|H";
                return $q.reject("I");
            }
        ).finally(
            function() { $scope.result += "|J"; }
        )
    }
]);
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="MyModule" ng-controller="MyCtrl">
  result: {{result}}
</div>
Pyrognostics answered 22/9, 2014 at 13:15 Comment(0)
P
2

This is my conclusion after reading the link provided by @bmceldowney:

The flow will always go to the next then, so in order to stop, don't provide the next then on the path / promise that needs to stop, and put the next then only on the path / promise that needs to continue.

In my case I don't want the chain to continue after receiving error on the first promise, so the next then should be appended on the second promise, not on the first then:

d0.promise.then(
    function(response) {
        // ...
        return d1.promise.then(     // append the next then here ...
            // ...
        ).catch (
            // ...
        ).finally(
            // ...
        );
    }
).catch (
    // ...
).finally(
    // ...
);                                  // ... instead of here

angular.module("MyModule", []).controller("MyCtrl", ["$scope", "$q", "$timeout",
    function($scope, $q, $timeout) {
        $scope.result = [];

        var d0 = $q.defer();
        $timeout(function() { d0.reject(); }, 1000);
        
        d0.promise.then(
            function(response) {
                $scope.result.push("d0 successful");

                var d1 = $q.defer();
                $timeout(function() { d1.reject(); }, 1000);
                
                return d1.promise.then(
                    function(response) { $scope.result.push("d1 successful"); }
                ).catch (
                    function(response) { $scope.result.push("d1 failed"); }
                ).finally(
                    function() { $scope.result.push("finally2"); }
                );
            }
        ).catch (
            function(response) { $scope.result.push("d0 failed"); }
        ).finally(
            function() { $scope.result.push("finally1"); }
        );
    }
]);
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="MyModule" ng-controller="MyCtrl">
  <p>result:</p>
  <div ng-repeat="msg in result">{{msg}}</div>
</div>
Pyrognostics answered 23/9, 2014 at 12:56 Comment(0)
O
4

From the $q.reject documentation:

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.

reject won't automatically abort the promise chain, it will just continue to the next promise, calling the error handler for each promise remaining.

Also, the finally callback is always going to run regardless of whether the chain is errored or not. If you don't want it to run then it's up to you to check the status of the promise manually.

EDIT:

Here's a link to an answer that shows how to chain errors:

Break promise chain and call a function based on the step in the chain where it is broken (rejected)

Otila answered 22/9, 2014 at 14:4 Comment(0)
P
2

This is my conclusion after reading the link provided by @bmceldowney:

The flow will always go to the next then, so in order to stop, don't provide the next then on the path / promise that needs to stop, and put the next then only on the path / promise that needs to continue.

In my case I don't want the chain to continue after receiving error on the first promise, so the next then should be appended on the second promise, not on the first then:

d0.promise.then(
    function(response) {
        // ...
        return d1.promise.then(     // append the next then here ...
            // ...
        ).catch (
            // ...
        ).finally(
            // ...
        );
    }
).catch (
    // ...
).finally(
    // ...
);                                  // ... instead of here

angular.module("MyModule", []).controller("MyCtrl", ["$scope", "$q", "$timeout",
    function($scope, $q, $timeout) {
        $scope.result = [];

        var d0 = $q.defer();
        $timeout(function() { d0.reject(); }, 1000);
        
        d0.promise.then(
            function(response) {
                $scope.result.push("d0 successful");

                var d1 = $q.defer();
                $timeout(function() { d1.reject(); }, 1000);
                
                return d1.promise.then(
                    function(response) { $scope.result.push("d1 successful"); }
                ).catch (
                    function(response) { $scope.result.push("d1 failed"); }
                ).finally(
                    function() { $scope.result.push("finally2"); }
                );
            }
        ).catch (
            function(response) { $scope.result.push("d0 failed"); }
        ).finally(
            function() { $scope.result.push("finally1"); }
        );
    }
]);
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="MyModule" ng-controller="MyCtrl">
  <p>result:</p>
  <div ng-repeat="msg in result">{{msg}}</div>
</div>
Pyrognostics answered 23/9, 2014 at 12:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.