-
Notifications
You must be signed in to change notification settings - Fork 27.5k
Commit
The $interval service simplifies creating and testing recurring tasks. This service does not increment $browser's outstanding request count, which means that scenario tests and Protractor tests will not timeout when a site uses a polling function registered by $interval. Provides a workaround for #2402. For unit tests, repeated tasks can be controlled using ngMock$interval's tick(), tickNext(), and tickAll() functions.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
'use strict'; | ||
|
||
|
||
function $IntervalProvider() { | ||
this.$get = ['$rootScope', '$window', '$q', | ||
function($rootScope, $window, $q) { | ||
var intervals = {}; | ||
|
||
|
||
/** | ||
* @ngdoc function | ||
* @name ng.$interval | ||
* | ||
* @description | ||
* Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` | ||
* milliseconds. | ||
* | ||
* The return value of registering an interval function is a promise. This promise will be | ||
* notified upon each tick of the interval, and will be resolved after `count` iterations, or | ||
* run indefinitely if `count` is not defined. The value of the notification will be the | ||
* number of iterations that have run. | ||
* To cancel an interval, call `$interval.cancel(promise)`. | ||
* | ||
* In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to | ||
* move forward by `millis` milliseconds and trigger any functions scheduled to run in that | ||
* time. | ||
* | ||
* @param {function()} fn A function that should be called repeatedly. | ||
* @param {number} delay Number of milliseconds between each function call. | ||
* @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat | ||
* indefinitely. | ||
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise | ||
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. | ||
* @returns {promise} A promise which will be notified on each iteration. | ||
*/ | ||
function interval(fn, delay, count, invokeApply) { | ||
var setInterval = $window.setInterval, | ||
clearInterval = $window.clearInterval; | ||
|
||
var deferred = $q.defer(), | ||
promise = deferred.promise, | ||
count = (isDefined(count)) ? count : 0, | ||
iteration = 0, | ||
skipApply = (isDefined(invokeApply) && !invokeApply); | ||
|
||
promise.then(null, null, fn); | ||
|
||
promise.$$intervalId = setInterval(function tick() { | ||
deferred.notify(iteration++); | ||
|
||
if (count > 0 && iteration >= count) { | ||
deferred.resolve(iteration); | ||
clearInterval(promise.$$intervalId); | ||
delete intervals[promise.$$intervalId]; | ||
} | ||
|
||
if (!skipApply) $rootScope.$apply(); | ||
|
||
}, delay); | ||
|
||
intervals[promise.$$intervalId] = deferred; | ||
|
||
return promise; | ||
} | ||
|
||
|
||
/** | ||
* @ngdoc function | ||
* @name ng.$interval#cancel | ||
* @methodOf ng.$interval | ||
* | ||
* @description | ||
* Cancels a task associated with the `promise`. | ||
* | ||
* @param {number} promise Promise returned by the `$interval` function. | ||
* @returns {boolean} Returns `true` if the task was successfully canceled. | ||
*/ | ||
interval.cancel = function(promise) { | ||
if (promise && promise.$$intervalId in intervals) { | ||
intervals[promise.$$intervalId].reject('canceled'); | ||
clearInterval(promise.$$intervalId); | ||
delete intervals[promise.$$intervalId]; | ||
return true; | ||
} | ||
return false; | ||
}; | ||
|
||
return interval; | ||
}]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -438,6 +438,119 @@ angular.mock.$LogProvider = function() { | |
}; | ||
|
||
|
||
/** | ||
* @ngdoc service | ||
* @name ngMock.$interval | ||
* | ||
* @description | ||
* Mock implementation of the $interval service. | ||
* | ||
* Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to | ||
* move forward by `millis` milliseconds and trigger any functions scheduled to run in that | ||
* time. | ||
* | ||
* @param {function()} fn A function that should be called repeatedly. | ||
* @param {number} delay Number of milliseconds between each function call. | ||
* @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat | ||
* indefinitely. | ||
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise | ||
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. | ||
* @returns {promise} A promise which will be notified on each iteration. | ||
*/ | ||
angular.mock.$IntervalProvider = function() { | ||
this.$get = ['$rootScope', '$q', | ||
function($rootScope, $q) { | ||
var repeatFns = [], | ||
nextRepeatId = 0, | ||
now = 0; | ||
|
||
var $interval = function(fn, delay, count, invokeApply) { | ||
var deferred = $q.defer(), | ||
promise = deferred.promise, | ||
count = (isDefined(count)) ? count : 0, | ||
This comment has been minimized.
Sorry, something went wrong. |
||
iteration = 0, | ||
skipApply = (isDefined(invokeApply) && !invokeApply); | ||
This comment has been minimized.
Sorry, something went wrong.
mjtko
Contributor
|
||
|
||
promise.then(null, null, fn); | ||
|
||
promise.$$intervalId = nextRepeatId; | ||
|
||
function tick() { | ||
deferred.notify(iteration++); | ||
|
||
if (count > 0 && iteration >= count) { | ||
var fnIndex; | ||
deferred.resolve(iteration); | ||
|
||
angular.forEach(repeatFns, function(fn, index) { | ||
if (fn.id === promise.$$intervalId) fnIndex = index; | ||
}); | ||
|
||
if (fnIndex !== undefined) { | ||
repeatFns.splice(fnIndex, 1); | ||
} | ||
} | ||
|
||
if (!skipApply) $rootScope.$apply(); | ||
}; | ||
|
||
repeatFns.push({ | ||
nextTime:(now + delay), | ||
delay: delay, | ||
fn: tick, | ||
id: nextRepeatId, | ||
deferred: deferred | ||
}); | ||
repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;}); | ||
|
||
nextRepeatId++; | ||
return promise; | ||
}; | ||
|
||
$interval.cancel = function(promise) { | ||
var fnIndex; | ||
|
||
angular.forEach(repeatFns, function(fn, index) { | ||
if (fn.id === promise.$$intervalId) fnIndex = index; | ||
}); | ||
|
||
if (fnIndex !== undefined) { | ||
repeatFns[fnIndex].deferred.reject('canceled'); | ||
repeatFns.splice(fnIndex, 1); | ||
return true; | ||
} | ||
|
||
return false; | ||
}; | ||
|
||
/** | ||
* @ngdoc method | ||
* @name ngMock.$interval#flush | ||
* @methodOf ngMock.$interval | ||
* @description | ||
* | ||
* Runs interval tasks scheduled to be run in the next `millis` milliseconds. | ||
* | ||
* @param {number=} millis maximum timeout amount to flush up until. | ||
* | ||
* @return {number} The amount of time moved forward. | ||
*/ | ||
$interval.flush = function(millis) { | ||
now += millis; | ||
while (repeatFns.length && repeatFns[0].nextTime <= now) { | ||
var task = repeatFns[0]; | ||
task.fn(); | ||
task.nextTime += task.delay; | ||
repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;}); | ||
} | ||
return millis; | ||
}; | ||
|
||
return $interval; | ||
}]; | ||
}; | ||
|
||
|
||
(function() { | ||
var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; | ||
|
||
|
@@ -1581,6 +1694,7 @@ angular.module('ngMock', ['ng']).provider({ | |
$browser: angular.mock.$BrowserProvider, | ||
$exceptionHandler: angular.mock.$ExceptionHandlerProvider, | ||
$log: angular.mock.$LogProvider, | ||
$interval: angular.mock.$IntervalProvider, | ||
$httpBackend: angular.mock.$HttpBackendProvider, | ||
$rootElement: angular.mock.$RootElementProvider | ||
}).config(function($provide) { | ||
|
6 comments
on commit 2b5ce84
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I make an http request (most common polling scenario), won't the http request increment the $browser's outstanding request count?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My protractor still getting timeout for long running http requests. We changed angular code to use $interval for making http calls but still same error.
my code looks like this.$interval(() => this.$http.get(this.prefix(url), config), 0, 1);
I am using anguarjs 1.6x and protractor 5.x.
Am i missing anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't sound related to $timeout
. The testability API (used by Protractor under the hood) will also wait for $http
requests to settle before considering the app stable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what the problem is to begin with. Why is the request taking so long? Is it expected? Is it desired? Do you need the request? Do you need protractor to wait for the response?
That said, this is a general support question. You can use one of the appropriate support channels for these types of questions.
GitHub issues are reserved for bug reports and feature requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gkalpak My application using event-streams and the request keep open so when protractor tests it waits for the request to finish but here it will never finish and finally gets timeout. Yes This is expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mollurumca, if you don't care about the request in tests, you can mock it out using ngMockE2E
's $httpBackend. Again, I am sure you will get plenty of good suggestions and insights on the support channels 😁
I believe this should be
angular.isDefined
rather thanisDefined
. I would (and can) submit a PR for this change, but I am not familiar enough with the tests for ngMocks to be able to also supply a spec, so it may be better that somebody in the angularjs team fix this and cover it with a spec if need be!