2

struggling to get promises working correctly in angularjs service provider i've read the docs as well as numerous examples (here, here and here) and i think i've got my syntax ok (although obviously something is wrong)

app module and controller look like

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

myApp.controller('Controller_1', ['$scope', 'Service_1', function($scope, Service_1) {

    var myName = "Ben";

    Service_1.slowService(myName)
        .then(Service_1.fastService(name));

    $scope.myName = myName;
}]);

the service (with the slow function) looks like this:

myApp.service('Service_1', function($q) {
    this.slowService = function (name) {
        var deferred = $q.defer();
        console.log('Start of slowService:', name, Date.now());

        setTimeout(function() {
            console.log('setTimeout name:', name, Date.now());

            if(name){
                name = 'Hello, ' + name + " is learning Angularjs";
                alert(name); 
                    console.log('name:', name);
                deferred.resolve(name);
            } else {
                deferred.reject('No name supplied !');
            }
        }, 3000);

        return deferred.promise;
    };

    this.fastService = function(name){ 
        console.log('Start of fastFunction:', name, Date.now());
        alert('Hello ' + name + ' - you are quick!'); 
    }; 
});

console output looks like this:

Start of slowService: Ben 1420832940118
Start of fastFunction: result 1420832940122
setTimeout name: Ben 1420832948422
name: Hello, Ben is learning Angularjs

The fastService is starting before the slowService completes, despite using a deferred object / promise in Service_1 and a .then in the controller...

Can anyone point out what's wrong with the code ?

jsfiddle is here

EDIT: put the fast function in the service so there's no confusion with hoisting etc - still the same result - js fiddle updated

Community
  • 1
  • 1
goredwards
  • 2,486
  • 2
  • 30
  • 40

4 Answers4

5

The fastService is starting before the slowService completes

That is because you are executing the function fastService before slowService async callback runs.Instead you would want to provide the reference of the function. i.e .then(Service_1.fastService(name)); should be .then(Service_1.fastService); or .then(function(name){ Service_1.fastService(name); }); otherwise fastservice will just run right away, before the async part of the slowService runs.

Use $timeout with that the advantage is that it already returns a promise so you do not need to create a deferred object and resulting in deferred anti-pattern.

myApp.service('Service_1', function($q, $timeout) { //<-- Inject timeout
    this.slowService = function (name) {
        console.log('Start of slowService:', name, Date.now());

        return $timeout(function() {
            console.log('setTimeout name:', name, Date.now());

            if(name){
                name = 'Hello, ' + name + " is learning Angularjs";
                return name; //return data
           }
           //Reject the promise
           return $q.reject('No name supplied !');

        }, 3000);


    };
   //...
});

and when consuming just chain through:

  Service_1.slowService(myName)
     .then(Service_1.fastService);

So instead of timeout even if you are using $http in your original method just return the promise from http rather than creating a deferred object. Also just keep in mind when you use the syntax .then(Service_1.fastService); and if you are referring to this context in the fast service it wont be the service instance.

Community
  • 1
  • 1
PSL
  • 123,204
  • 21
  • 253
  • 243
  • thanks - very helpful - along with Yaak's input I've got it working. Can you explain Zack Patterson's remark (which I had also noticed) that "fastService returned 'result' as name instead of saying name is undefined" ? If `.then` is _only_ used for promises, and the 1st function called (`slowService`) has a deferred object, how can the browser simply skip over the deferred object - no matter what is in the `.then()` brackets ? The fact that `fastService` outputs `result` instead of `undefined` seems like the browser knows it should expect a promise `result`, but just doesn't wait for it... – goredwards Jan 09 '15 at 23:40
  • PS thanks also for the hints on $timeout - i actually only included a timeout in `slowService` as a way of simulating $http calls and SQLite localdatabase queries. – goredwards Jan 09 '15 at 23:42
  • also just a point - where you say `That is because you are executing the function fastService before slowService,` - can you explain this ? From the console output, `slowService` definitely starts _before_ `fastService` - it's just that `fastService` doesn't wait until `slowService` has finished before starting... correct ? – goredwards Jan 09 '15 at 23:48
  • Gotta hate my English I cleared it in the end of the statement saying before the asyc callback of the slowservice executes. IL update that statement. I knew u were using timeout for demo that is why I added the last statement. No need to create deferred object when dealing with a service that already returns a promise. And yes from the q promise call back u can return data or another promise the ultimately resolved data is what u will get as result in the chain. – PSL Jan 10 '15 at 00:10
2

The way you are passing your function to then() part is not correct. Pass it like this :

Service_1.slowService(myName)
         .then(function(){fastFunction(name)});
YAAK
  • 328
  • 1
  • 14
1

Since your slowService resolves with a name value, that is what is going to be provided as the parameter of the function you pass into then. You could do this:

Service_1.slowService(myName)
.then(function(name){ Service_1.fastFunction(name); });

but even this is redundant since you are just handing off the name to another function that takes a single name parameter.

This is all you need:

Service_1.slowService(myName)
.then(Service_1.fastFunction);

Notice that there is no (name) in that second line. We want to pass the function into then. What you were doing was calling the function immediately and passing undefined into then.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • thanks for this - very clear - although as @Zack Patterson points out, the `fastService` returned 'result' as name instead of saying that name is undefined - do you know why this is ? It's like the `.then` for knows it should expect a promise, but just doesn't wait for it... – goredwards Jan 09 '15 at 23:31
  • @goredwards The `name` variable that you're passing to `Service_1.fastFunction` isn't declared anywhere in your code, so when your call `Service_1.fastFunction(name)`, you are passing the [global `name` variable](https://developer.mozilla.org/en-US/docs/Web/API/Window.name). jsfiddle code runs inside a window named "result", so that is why you are seeing "result". Try passing a variable that isn't globally defined and you will see a very different outcome: http://jsfiddle.net/de8fn1vd/5/ – JLRishe Jan 10 '15 at 05:49
1

YAAK is correct. You could also write it like this:

Service_1.slowService(myName) .then(fastService);

Just to clarify a bit on what I'm pretty sure is going on:

The then function is registering callbacks for when the promise is either resolved or rejected. You don't need to pass the function parameters, because that is already done when you resolve with whatever data you are trying to pass to the callback function. When you had fastService(name) in there, it was just executing the function as soon as it hit that line of code without waiting for the promise to resolve since it was a function call, and not a function object.

It's interesting that fastService returned 'result' as name instead of saying that name is undefined. I don't know where it is getting a value for that variable from.