Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix($timeout/$interval): if invokeApply is false, do not use evalAsync
Browse files Browse the repository at this point in the history
$evalAsync triggers a digest, and is unsuitable when it is expected that a digest should not occur.

BREAKING CHANGE

Previously, even if invokeApply was set to false, a $rootScope digest would occur during promise
resolution. This is no longer the case, as promises returned from $timeout and $interval will no
longer trigger $evalAsync (which in turn causes a $digest) if `invokeApply` is false.

Workarounds include manually triggering $scope.$apply(), or returning $q.defer().promise from a
promise callback, and resolving or rejecting it when appropriate.

    var interval = $interval(function() {
      if (someRequirementFulfilled) {
        $interval.cancel(interval);
        $scope.$apply();
      }
    }, 100, 0, false);

or:

    var interval = $interval(function (idx) {
      // make the magic happen
    }, 1000, 10, false);
    interval.then(function(idx) {
      var deferred = $q.defer();
      // do the asynchronous magic --- $evalAsync will cause a digest and cause
      // bindings to update.
      return deferred.promise;
    });

Closes #7999
Closes #7103
  • Loading branch information
caitp committed Jun 27, 2014
1 parent b28b5ca commit 19b6b34
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
$ParseProvider,
$RootScopeProvider,
$QProvider,
$$QProvider,
$$SanitizeUriProvider,
$SceProvider,
$SceDelegateProvider,
Expand Down Expand Up @@ -222,6 +223,7 @@ function publishExternalAPI(angular){
$parse: $ParseProvider,
$rootScope: $RootScopeProvider,
$q: $QProvider,
$$q: $$QProvider,
$sce: $SceProvider,
$sceDelegate: $SceDelegateProvider,
$sniffer: $SnifferProvider,
Expand Down
10 changes: 5 additions & 5 deletions src/ng/interval.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@


function $IntervalProvider() {
this.$get = ['$rootScope', '$window', '$q',
function($rootScope, $window, $q) {
this.$get = ['$rootScope', '$window', '$q', '$$q',
function($rootScope, $window, $q, $$q) {
var intervals = {};


Expand Down Expand Up @@ -133,10 +133,10 @@ function $IntervalProvider() {
function interval(fn, delay, count, invokeApply) {
var setInterval = $window.setInterval,
clearInterval = $window.clearInterval,
deferred = $q.defer(),
promise = deferred.promise,
iteration = 0,
skipApply = (isDefined(invokeApply) && !invokeApply);
skipApply = (isDefined(invokeApply) && !invokeApply),
deferred = (skipApply ? $$q : $q).defer(),
promise = deferred.promise;

count = isDefined(count) ? count : 0;

Expand Down
7 changes: 7 additions & 0 deletions src/ng/q.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ function $QProvider() {
}];
}

function $$QProvider() {
this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
return qFactory(function(callback) {
$browser.defer(callback);
}, $exceptionHandler);
}];
}

/**
* Constructs a promise manager.
Expand Down
8 changes: 4 additions & 4 deletions src/ng/timeout.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@


function $TimeoutProvider() {
this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
function($rootScope, $browser, $q, $exceptionHandler) {
this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
function($rootScope, $browser, $q, $$q, $exceptionHandler) {
var deferreds = {};


Expand Down Expand Up @@ -33,9 +33,9 @@ function $TimeoutProvider() {
*
*/
function timeout(fn, delay, invokeApply) {
var deferred = $q.defer(),
var skipApply = (isDefined(invokeApply) && !invokeApply),
deferred = (skipApply ? $$q : $q).defer(),
promise = deferred.promise,
skipApply = (isDefined(invokeApply) && !invokeApply),
timeoutId;

timeoutId = $browser.defer(function() {
Expand Down
17 changes: 17 additions & 0 deletions test/ng/intervalSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,23 @@ describe('$interval', function() {
}));


it('should NOT call $evalAsync or $digest if invokeApply is set to false',
inject(function($interval, $rootScope, $window, $timeout) {
var evalAsyncSpy = spyOn($rootScope, '$evalAsync').andCallThrough();
var digestSpy = spyOn($rootScope, '$digest').andCallThrough();
var notifySpy = jasmine.createSpy('notify');

$interval(notifySpy, 1000, 1, false);

$window.flush(2000);
$timeout.flush(); // flush $browser.defer() timeout

expect(notifySpy).toHaveBeenCalledOnce();
expect(evalAsyncSpy).not.toHaveBeenCalled();
expect(digestSpy).not.toHaveBeenCalled();
}));


it('should allow you to specify the delay time', inject(function($interval, $window) {
var counter = 0;
$interval(function() { counter++; }, 123);
Expand Down
16 changes: 16 additions & 0 deletions test/ng/timeoutSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ describe('$timeout', function() {
}));


it('should NOT call $evalAsync or $digest if invokeApply is set to false',
inject(function($timeout, $rootScope) {
var evalAsyncSpy = spyOn($rootScope, '$evalAsync').andCallThrough();
var digestSpy = spyOn($rootScope, '$digest').andCallThrough();
var fulfilledSpy = jasmine.createSpy('fulfilled');

$timeout(fulfilledSpy, 1000, false);

$timeout.flush();

expect(fulfilledSpy).toHaveBeenCalledOnce();
expect(evalAsyncSpy).not.toHaveBeenCalled();
expect(digestSpy).not.toHaveBeenCalled();
}));


it('should allow you to specify the delay time', inject(function($timeout, $browser) {
var defer = spyOn($browser, 'defer');
$timeout(noop, 123);
Expand Down

0 comments on commit 19b6b34

Please sign in to comment.