8

In my controller I'm getting a promise from another service. I add a 'then' clause to it, but the 'then' is never called.

See this plunker: http://plnkr.co/edit/dX0Oz1?p=preview (javascript version)

'fakeLongRunningPromise' creates a promise that resolves itself after 2 seconds.

In the controller itself I send a note to the console once the promise is resolved.

I can tell that the promise is being resolved because "Resolving promise" it outputted to the console. Why doesn't it output "promise resolved"?

Thinking maybe the promise is going 'out of scope' because the controller returns?

Roy Truelove
  • 22,016
  • 18
  • 111
  • 153

2 Answers2

12

The AngularJS the result of promises resolution is propagated asynchronously, inside a $digest cycle. So, the callbacks registered with then will be only called upon entering the $digest cycle. The setTimeout executes "outside of the AngularJS world", and as such will not trigger callbacks.

The solution is to use Scope.$apply or the $timeout service. Here is the version with $apply:

      window.setTimeout(function() {
        console.log("Resolving promise");
        $scope.$apply(function(){
          deffered.resolve("worked");
        });
      }, 2000);

Here is a fixed plunk (JavaScript): http://plnkr.co/edit/g5AnUK6oq2OBz7q2MEh7?p=preview

pkozlowski.opensource
  • 117,202
  • 60
  • 326
  • 286
  • Thanks Pawel, my actual case doesn't use setTimeout but the fact that it needs a $digest cycle is interesting - did not expect that. Could be part of the problem. So everytime one uses (say) $http.get(), the resultant promise won't be triggered unless there's a digest? Seems odd. – Roy Truelove Jan 09 '13 at 21:36
  • $http invokes $digest cycle (by calling $apply), same for DOM events and the $timeout service. So as soon as you resolve promises in the AngularJS world results are propagated imediatelly, without a need to manually call $apply. So, in reality $digest is needed to propagate resolution / rejection results, but most of the time you don't see this, since AngularJS takes care of calling $apply. – pkozlowski.opensource Jan 09 '13 at 21:41
  • Huh. In my actual case I'm using $http but promises that are in my controller function do not resolve. Only if I put them in a $scope function do they resolve. I'll keep banging away but I think the $digest is the key. Thanks again. – Roy Truelove Jan 10 '13 at 13:37
  • Good ol' $scope.$apply. Wrapped my new chained promise in an $apply and it works like a charm. Phew! – Roy Truelove Jan 10 '13 at 16:33
  • @Roy, I'm really not clear why you need to use $apply while working with $http - if I understand things correctly this shouldn't be normally needed. If you could post your actual code on the AngularJS mailing list I could have a look. – pkozlowski.opensource Jan 10 '13 at 18:10
  • I think it's a simple matter of my added a new 'then' clause to my $http promise inside a jQuery callback, so Angular didn't know about it, so didn't call $digest to propegate the promise. Once i wrapped that callback with an apply it was ok. – Roy Truelove Jan 10 '13 at 18:24
  • Thanks, this is so cool that I got here. Your answer is very simple and becomes easy to understand and apply in a while different situation. – Priya Ranjan Singh Apr 13 '17 at 13:34
2

I've used $timeout instead of setTimeout and it works:

 # Resolve the promise after 2 seconds
  $timeout( ()->
    console.log "Resolving promise"
    deffered.resolve ("worked")
  , 2000)
asgoth
  • 35,552
  • 12
  • 89
  • 98
  • 2
    Of course that it works since $timeout is just setTimout that invokes $apply. In other words, it is the $timeout service that triggers a digest cycle. But the main point here is that results of promise resolution are only propagated when you are in the AngularJS digest cycle. – pkozlowski.opensource Jan 09 '13 at 21:42