Asked  7 Months ago    Answers:  5   Viewed   31 times

I have seen answers on StackOverflow where people suggest furnishing a callback function to an AngularJS service.

app.controller('tokenCtrl', function($scope, tokenService) {
    tokenService.getTokens(function callbackFn(tokens) {
        $scope.tokens = tokens;
    });
});

app.factory('tokenService', function($http) {
    var getTokens = function(callbackFn) {
        $http.get('/api/tokens').then (function onFulfilled(response) {
            callbackFn(response.data);
        });
    };

    return {
        getTokens: getTokens
    };
});

This seems to me to be an Anti-Pattern. The $http service returns promises and having .then methods execute callback functions feels like an unhealthy inversion of control.

How does one re-factor code like this and how does one explain why the original way was not a good idea?

 Answers

11

The code can be re-factored as follows:

app.controller('tokenCtrl', function($scope, tokenService) {
    tokenService.getTokens.then ( callbackFn(tokens) {
        $scope.tokens = tokens;
    });
});

app.factory('tokenService', function($http) {
    var getTokens = function() {
        //return promise
        return $http.get('/api/tokens').then (function onFulfilled(response) {
                //return tokens
                return response.data;
            }
        );
    };

    return {
        getTokens: getTokens
    };
});

By having the service return a promise, and using the .then method of the promise, the same functionality is achieved with the following benefits:

  • The promise can be saved and used for chaining.

  • The promise can be saved and used to avoid repeating the same $http call.

  • Error information is retained and can be retrieved with the .catch method.

  • The promise can be forwarded to other clients.

Tuesday, June 1, 2021
 
PeterTheLobster
answered 7 Months ago
50

To help with answering, here is more about the anti-pattern comment:

it is overused, introduces unnecessary restrictions in situations where a sole instance of a class is not actually required, and introduces global state into an application

From: http://en.wikipedia.org/wiki/Singleton_pattern

For more on this you can look at: https://www.michaelsafyan.com/tech/design/patterns/singleton

Here is a great ending to the blog above:

In short, the singleton pattern makes code more complex, less useful, and a real pain to re-use or test. Eliminating singletons can be tricky, but it’s a worthwhile endeavour.

OK, so, the reason it is an anti-pattern is described well in this paragraph, and, as the author expresses, it tightly couples your code to the singleton.

If you find that you want to use a singleton, you may want to consider your design, but there are times where it is useful.

For example, once I had to write an application that could have at most one database connection, to process thousands of requests. So, a singleton makes sense since I am resource constrained to having only one instance.

But, generally this is used to simplify code, without thinking of the difficulties that will be introduced.

For example, and this applies to static classes also, if you unit test, or have concurrency, then the state of one request will change the state and that may cause problems, as the class calling the instance may be assuming the state is as it expected.

I think the best way to challenge the use is to think of how to handle it if your program is multi-threaded, and a simple way to do that is to unit test it, if you have several tests that run at one time.

If you find that you still need it, then use it, but realize the problems that will be encountered later.

Tuesday, June 1, 2021
 
Terry
answered 7 Months ago
65

You are asking how to chain functions in promises.

3) How can I transfer data between them?

4) How can I make the following function depend on its previous result?

Return data (or a promise) for the next function in the chain:

var p2 = p1.then ( function (data) {
     var nextData = someFn(data);
     return nextData;
});

var p3 = p2.then ( function (nextData) {
     var nextData2 = someOtherFn(nextData);
     return nextData2;
});

//return for further chaining
return p3;

1) If e.g. checkTokenOnline is not OK, I don't want to execute the rest functions, how can I quit (exit, break, whatever,..) at this point?

To reject a promise, have your function throw an error. The chain will skip all the .then methods until you supply an error handler.

var p2 = p1.then ( function checkTokenOnline (response) {
             if ( isBadFn(response) {
                 throw error;
             } else {
                 return nextData;
             }
   }) .then ( someFn 
    ) .then ( someOtherFn
    ) .catch ( function (error) {
          // someFn and someOtherFn skipped
          //log error
          throw error;
   });

 //return for further chaining
 return p2;

2) How can I set some of them parallel and some of them serial ?

To make two functions run in parallel, make two promises. Use $q.all to wait for them both to complete.

var p1 = $q.when ( fn1() );
var p2 = $q.when ( fn2() );

var p3 = $q.all ( [p1, p2] );

var p4 = p3.then ( function (responseList) {
      var response1 = responseList[0];
      var response2 = responseList[1];
      return something;
}). catch ( function (error) {
      //log error
      throw error;
});

//return for further chaining
return p4;

Be aware that $q.all is not resilient. If any promise throws an error, the .then method will be skipped and only the first error will go to the .catch method.

The rule of thumb for functional programming is always return something.


Useful links

  • AngularJS $q Reference - Chaining promises
  • You're Missing the Point of Promises.
  • Ninja Squad -- Traps, anti-patterns and tips about AngularJS promises
Tuesday, June 1, 2021
 
JustSteveKing
answered 7 Months ago
22

I assume the answer is largely because why are you catching it if you can't handle it? Why not let whomever can handle it (or whomever is left with no choice but to handle it) log it, if they feel that it is log-worthy?

If you catch it and log it and rethrow it, then there's no way for the upstream code to know that you've already logged the exception, and so the same exception might get logged twice. Or worse, if all the upstream code follows this same pattern, the exception might be logged an arbitrary number of times, once for each level in the code that decides to catch it, log it, and then throw it again.

Also some might argue that since throwing and catching exceptions are relatively costly operations, all this catching and rethrowing isn't helping your runtime performance. Nor is it helping your code in terms of conciseness or maintainability.

Monday, July 12, 2021
 
daiscog
answered 5 Months ago
79

I'd like to think this is what you are looking for:

service.factory("ScheduleFactory", ['$http', '$resource', '$q', 'dataFactory', function($http, $resource, $q, dataFactory) {
     var objFactory = {};

     objFactory.getDaysOfWeek = function(includeWeekends) {
          var API  = $resource(restURL + '/daysOfWeek/'), defObj = $q.defer();

          var daysQuery = API.query();
          daysQuery.$promise.then(function(data) {
                    //you can add anything else you want inside this function
                    defObj.resolve(data);
               }, function(error) {
                    //you can add anything else you want inside this function
                    defObj.resolve(dataFactory.daysOfWeek);
               });

            return defObj.promise;
        };

I've skipped any data formatting and assignment details because you're liable to change them anyway. By wrapping your API query in an extra promise you gain the ability to manipulate what is returned. If your API can be reached, you return the data from it in the resolution of your outer promise. Otherwise, you return your hard-coded data from your other service in the resolution of your outer promise.

Monday, October 4, 2021
 
joostvandriel
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share