Angular - Is .finally() Called At the End of Every Path in a Chained Promise Decision Tree?
Asked Answered
E

2

8

I have the following chained sequence of promises:

$scope.promisesInProgress = true
myService.myFirstPromise(id)
    .then(function(data){
        $scope.firstResponse = data;
        return myService.mySecondPromise(id);
    })
    .then(function(data){
        $scope.secondResponse = data;
    })
    .finally(function(){
        $scope.promisesInProgress = false;
    });

Is the finally() callback function being called at the very end no matter what the success / failure of the previous two promises are?

For example, if myFirstPromise() returned a 400 response, mySecondPromise() will never be called - but I assume the finally() block would still be thrown? The same should be true if mySecondPromise() returns a 400 (and $scope.secondResponse is never set) and if both promises return 200s.

Eccentricity answered 6/5, 2016 at 15:19 Comment(0)
E
1

I wrote a Jasmine test to see whether the finally() block is called on each function execution no matter what the chained promises returned.

describe('myController Test Suite', function(){

    var q, scope, deferred, myService;

    // Initialize the Pointivo module
    beforeEach(function(){
        module('myApp');
    });

    // Mock out fake service
    beforeEach(function(){
        myService = {
            myFirstPromise: function(){
                deferred = q.defer();
                // TEST ME
                deferred.resolve('first promise response');
                return deferred.promise;
            },
            mySecondPromise: function(){
                deferred = q.defer();
                // TEST ME
                deferred.resolve('second promise response');
                return deferred.promise;
            },
            myThirdPromise: function(){
                deferred = q.defer();
                // TEST ME
                deferred.resolve('third promise response');
                return deferred.promise;
            }
        };
        spyOn(myService, 'myFirstPromise').and.callThrough();
        spyOn(myService, 'mySecondPromise').and.callThrough();
        spyOn(myService, 'myThirdPromise').and.callThrough();
    });

    // Assign controller scope and service references
    beforeEach(inject(function($controller, $rootScope, $q){
        scope = $rootScope.$new();
        q = $q;
        $controller('myController', {
            $scope: scope,
            myService: myService
        });
    }));

    describe('finally test', function(){
        it('should always hit the finally statement', function(){
                scope.finallyStatementFlag = false;
            scope.test();
            scope.$apply();
            expect(scope.finallyStatementFlag).toBeTruthy();
        });
    });

});

The above rests on the assumption that the controller looks like:

myApp.controller('myController', function($scope, myService){

    $scope.finallyStatementFlag = false;

    $scope.test = function(){
        myService.myFirstPromise()
            .then(function(data){
                console.log(data);
                return myService.mySecondPromise()
            })
            .then(function(data){
                console.log(data);
                return myService.myThirdPromise();
            })
            .then(function(data){
                console.log(data);
            })
            .finally(function(){
                console.log('finally statement');
                $scope.finallyStatementFlag = true;
            });
    }

});

The above will pass even if you change any or all of the deferred.resolve() to deferred.reject() inside of the beforeEach() callback where we define myService.

Fiddle example

Eccentricity answered 16/5, 2016 at 17:55 Comment(0)
W
3

Angular 1.x $q service inspired by Kris Kowal's Q, based on docs:

finally(callback, notifyCallback) – allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful to release resources or do some clean-up that needs to be done whether the promise was rejected or resolved. See the full specification for more information.

so yes, no matter myFirstPromise resolved or rejected, the finally() block would always be called

UPDATED,

to be noticed, the finally() block of myFirstPromise would be called before mySecondPromise resolved(or rejected), because myFirstPromise and mySecondPromise are different promise instance, and mySecondPromise promise instance created after myFirstPromise resolved

Washtub answered 6/5, 2016 at 15:36 Comment(0)
E
1

I wrote a Jasmine test to see whether the finally() block is called on each function execution no matter what the chained promises returned.

describe('myController Test Suite', function(){

    var q, scope, deferred, myService;

    // Initialize the Pointivo module
    beforeEach(function(){
        module('myApp');
    });

    // Mock out fake service
    beforeEach(function(){
        myService = {
            myFirstPromise: function(){
                deferred = q.defer();
                // TEST ME
                deferred.resolve('first promise response');
                return deferred.promise;
            },
            mySecondPromise: function(){
                deferred = q.defer();
                // TEST ME
                deferred.resolve('second promise response');
                return deferred.promise;
            },
            myThirdPromise: function(){
                deferred = q.defer();
                // TEST ME
                deferred.resolve('third promise response');
                return deferred.promise;
            }
        };
        spyOn(myService, 'myFirstPromise').and.callThrough();
        spyOn(myService, 'mySecondPromise').and.callThrough();
        spyOn(myService, 'myThirdPromise').and.callThrough();
    });

    // Assign controller scope and service references
    beforeEach(inject(function($controller, $rootScope, $q){
        scope = $rootScope.$new();
        q = $q;
        $controller('myController', {
            $scope: scope,
            myService: myService
        });
    }));

    describe('finally test', function(){
        it('should always hit the finally statement', function(){
                scope.finallyStatementFlag = false;
            scope.test();
            scope.$apply();
            expect(scope.finallyStatementFlag).toBeTruthy();
        });
    });

});

The above rests on the assumption that the controller looks like:

myApp.controller('myController', function($scope, myService){

    $scope.finallyStatementFlag = false;

    $scope.test = function(){
        myService.myFirstPromise()
            .then(function(data){
                console.log(data);
                return myService.mySecondPromise()
            })
            .then(function(data){
                console.log(data);
                return myService.myThirdPromise();
            })
            .then(function(data){
                console.log(data);
            })
            .finally(function(){
                console.log('finally statement');
                $scope.finallyStatementFlag = true;
            });
    }

});

The above will pass even if you change any or all of the deferred.resolve() to deferred.reject() inside of the beforeEach() callback where we define myService.

Fiddle example

Eccentricity answered 16/5, 2016 at 17:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.