mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-01-12 22:45:52 +08:00
feat($timeout): add $timeout service that supersedes $defer
$timeout has a better name ($defer got often confused with something related to $q) and is actually promise based with cancelation support. With this commit the $defer service is deprecated and will be removed before 1.0. Closes #704, #532
This commit is contained in:
1
angularFiles.js
vendored
1
angularFiles.js
vendored
@@ -29,6 +29,7 @@ angularFiles = {
|
||||
'src/ng/http.js',
|
||||
'src/ng/httpBackend.js',
|
||||
'src/ng/locale.js',
|
||||
'src/ng/timeout.js',
|
||||
|
||||
'src/ng/filter.js',
|
||||
'src/ng/filter/filter.js',
|
||||
|
||||
@@ -125,6 +125,7 @@ function publishExternalAPI(angular){
|
||||
$q: $QProvider,
|
||||
$sniffer: $SnifferProvider,
|
||||
$templateCache: $TemplateCacheProvider,
|
||||
$timeout: $TimeoutProvider,
|
||||
$window: $WindowProvider
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.module.ng.$defer
|
||||
* @deprecated Made obsolete by $timeout service. Please migrate your code. This service will be
|
||||
* removed with 1.0 final.
|
||||
* @requires $browser
|
||||
*
|
||||
* @description
|
||||
@@ -29,7 +31,9 @@
|
||||
* @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
|
||||
*/
|
||||
function $DeferProvider(){
|
||||
this.$get = ['$rootScope', '$browser', function($rootScope, $browser) {
|
||||
this.$get = ['$rootScope', '$browser', '$log', function($rootScope, $browser, $log) {
|
||||
$log.warn('$defer service has been deprecated, migrate to $timeout');
|
||||
|
||||
function defer(fn, delay) {
|
||||
return $browser.defer(function() {
|
||||
$rootScope.$apply(fn);
|
||||
|
||||
87
src/ng/timeout.js
Normal file
87
src/ng/timeout.js
Normal file
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
function $TimeoutProvider() {
|
||||
this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
|
||||
function($rootScope, $browser, $q, $exceptionHandler) {
|
||||
var deferreds = {};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.module.ng.$timeout
|
||||
* @requires $browser
|
||||
*
|
||||
* @description
|
||||
* Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
|
||||
* block and delegates any exceptions to
|
||||
* {@link angular.module.ng.$exceptionHandler $exceptionHandler} service.
|
||||
*
|
||||
* The return value of registering a timeout function is a promise which will be resolved when
|
||||
* the timeout is reached and the timeout function is executed.
|
||||
*
|
||||
* To cancel a the timeout request, call `$timeout.cancel(promise)`.
|
||||
*
|
||||
* In tests you can use {@link angular.module.ngMock.$timeout `$timeout.flush()`} to
|
||||
* synchronously flush the queue of deferred functions.
|
||||
*
|
||||
* @param {function()} fn A function, who's execution should be delayed.
|
||||
* @param {number=} [delay=0] Delay in milliseconds.
|
||||
* @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise
|
||||
* will invoke `fn` within the {@link angular.module.ng.$rootScope.Scope#$apply $apply} block.
|
||||
* @returns {*} Promise that will be resolved when the timeout is reached. The value this
|
||||
* promise will be resolved with is the return value of the `fn` function.
|
||||
*/
|
||||
function timeout(fn, delay, invokeApply) {
|
||||
var deferred = $q.defer(),
|
||||
promise = deferred.promise,
|
||||
skipApply = (isDefined(invokeApply) && !invokeApply),
|
||||
timeoutId, cleanup;
|
||||
|
||||
timeoutId = $browser.defer(function() {
|
||||
try {
|
||||
deferred.resolve(fn());
|
||||
} catch(e) {
|
||||
deferred.reject(e);
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
|
||||
if (!skipApply) $rootScope.$apply();
|
||||
}, delay);
|
||||
|
||||
cleanup = function() {
|
||||
delete deferreds[promise.$$timeoutId];
|
||||
};
|
||||
|
||||
promise.$$timeoutId = timeoutId;
|
||||
deferreds[timeoutId] = deferred;
|
||||
promise.then(cleanup, cleanup);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.module.ng.$timeout#cancel
|
||||
* @methodOf angular.module.ng.$timeout
|
||||
*
|
||||
* @description
|
||||
* Cancels a task associated with the `promise`. As a result of this the promise will be
|
||||
* resolved with a rejection.
|
||||
*
|
||||
* @param {Promise} promise Promise returned by the `$timeout` function.
|
||||
* @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
|
||||
* canceled.
|
||||
*/
|
||||
timeout.cancel = function(promise) {
|
||||
if (promise.$$timeoutId in deferreds) {
|
||||
deferreds[promise.$$timeoutId].reject('canceled');
|
||||
return $browser.defer.cancel(promise.$$timeoutId);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return timeout;
|
||||
}];
|
||||
}
|
||||
26
src/ngMock/angular-mocks.js
vendored
26
src/ngMock/angular-mocks.js
vendored
@@ -1328,6 +1328,25 @@ function MockXhr() {
|
||||
this.abort = angular.noop;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.module.ngMock.$timeout
|
||||
* @description
|
||||
*
|
||||
* This service is just a simple decorator for {@link angular.module.ng.$timeout $timeout} service
|
||||
* that adds a "flush" method.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ngMock.$timeout#flush
|
||||
* @methodOf angular.module.ngMock.$timeout
|
||||
* @description
|
||||
*
|
||||
* Flushes the queue of pending tasks.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name angular.module.ngMock
|
||||
@@ -1341,6 +1360,13 @@ angular.module('ngMock', ['ng']).provider({
|
||||
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
|
||||
$log: angular.mock.$LogProvider,
|
||||
$httpBackend: angular.mock.$HttpBackendProvider
|
||||
}).config(function($provide) {
|
||||
$provide.decorator('$timeout', function($delegate, $browser) {
|
||||
$delegate.flush = function() {
|
||||
$browser.defer.flush();
|
||||
};
|
||||
return $delegate;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ describe('$defer', function() {
|
||||
$provide.factory('$exceptionHandler', function(){
|
||||
return jasmine.createSpy('$exceptionHandler');
|
||||
});
|
||||
$provide.value('$log', {warn: noop});
|
||||
}));
|
||||
|
||||
|
||||
|
||||
146
test/ng/timeoutSpec.js
Normal file
146
test/ng/timeoutSpec.js
Normal file
@@ -0,0 +1,146 @@
|
||||
'use strict';
|
||||
|
||||
describe('$timeout', function() {
|
||||
|
||||
beforeEach(module(provideLog));
|
||||
|
||||
|
||||
it('should delegate functions to $browser.defer', inject(function($timeout, $browser) {
|
||||
var counter = 0;
|
||||
$timeout(function() { counter++; });
|
||||
|
||||
expect(counter).toBe(0);
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(counter).toBe(1);
|
||||
|
||||
expect(function() {$browser.defer.flush();}).toThrow('No deferred tasks to be flushed');
|
||||
expect(counter).toBe(1);
|
||||
}));
|
||||
|
||||
|
||||
it('should call $apply after each callback is executed', inject(function($timeout, $rootScope) {
|
||||
var applySpy = spyOn($rootScope, '$apply').andCallThrough();
|
||||
|
||||
$timeout(function() {});
|
||||
expect(applySpy).not.toHaveBeenCalled();
|
||||
|
||||
$timeout.flush();
|
||||
expect(applySpy).toHaveBeenCalledOnce();
|
||||
|
||||
applySpy.reset();
|
||||
|
||||
$timeout(function() {});
|
||||
$timeout(function() {});
|
||||
$timeout.flush();
|
||||
expect(applySpy.callCount).toBe(2);
|
||||
}));
|
||||
|
||||
|
||||
it('should NOT call $apply if skipApply is set to true', inject(function($timeout, $rootScope) {
|
||||
var applySpy = spyOn($rootScope, '$apply').andCallThrough();
|
||||
|
||||
$timeout(function() {}, 12, false);
|
||||
expect(applySpy).not.toHaveBeenCalled();
|
||||
|
||||
$timeout.flush();
|
||||
expect(applySpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
|
||||
it('should allow you to specify the delay time', inject(function($timeout, $browser) {
|
||||
var defer = spyOn($browser, 'defer');
|
||||
$timeout(noop, 123);
|
||||
expect(defer.callCount).toEqual(1);
|
||||
expect(defer.mostRecentCall.args[1]).toEqual(123);
|
||||
}));
|
||||
|
||||
|
||||
it('should return a promise which will be resolved with return value of the timeout callback',
|
||||
inject(function($timeout, log) {
|
||||
var promise = $timeout(function() { log('timeout'); return 'buba'; });
|
||||
|
||||
promise.then(function(value) { log('promise success: ' + value); }, log.fn('promise error'));
|
||||
expect(log).toEqual([]);
|
||||
|
||||
$timeout.flush();
|
||||
expect(log).toEqual(['timeout', 'promise success: buba']);
|
||||
}));
|
||||
|
||||
|
||||
describe('exception handling', function() {
|
||||
|
||||
beforeEach(module(function($exceptionHandlerProvider) {
|
||||
$exceptionHandlerProvider.mode('log');
|
||||
}));
|
||||
|
||||
|
||||
it('should delegate exception to the $exceptionHandler service', inject(
|
||||
function($timeout, $exceptionHandler) {
|
||||
$timeout(function() {throw "Test Error";});
|
||||
expect($exceptionHandler.errors).toEqual([]);
|
||||
|
||||
$timeout.flush();
|
||||
expect($exceptionHandler.errors).toEqual(["Test Error"]);
|
||||
}));
|
||||
|
||||
|
||||
it('should call $apply even if an exception is thrown in callback', inject(
|
||||
function($timeout, $rootScope) {
|
||||
var applySpy = spyOn($rootScope, '$apply').andCallThrough();
|
||||
|
||||
$timeout(function() {throw "Test Error";});
|
||||
expect(applySpy).not.toHaveBeenCalled();
|
||||
|
||||
$timeout.flush();
|
||||
expect(applySpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
|
||||
it('should reject the timeout promise when an exception is thrown in the timeout callback',
|
||||
inject(function($timeout, log) {
|
||||
var promise = $timeout(function() { throw "Some Error"; });
|
||||
|
||||
promise.then(log.fn('success'), function(reason) { log('error: ' + reason); });
|
||||
$timeout.flush();
|
||||
|
||||
expect(log).toEqual('error: Some Error');
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('cancel', function() {
|
||||
it('should cancel tasks', inject(function($timeout) {
|
||||
var task1 = jasmine.createSpy('task1'),
|
||||
task2 = jasmine.createSpy('task2'),
|
||||
task3 = jasmine.createSpy('task3'),
|
||||
promise1, promise3;
|
||||
|
||||
promise1 = $timeout(task1);
|
||||
$timeout(task2);
|
||||
promise3 = $timeout(task3, 333);
|
||||
|
||||
$timeout.cancel(promise3);
|
||||
$timeout.cancel(promise1);
|
||||
$timeout.flush();
|
||||
|
||||
expect(task1).not.toHaveBeenCalled();
|
||||
expect(task2).toHaveBeenCalledOnce();
|
||||
expect(task3).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
|
||||
it('should return true if a task was successfully canceled', inject(function($timeout) {
|
||||
var task1 = jasmine.createSpy('task1'),
|
||||
task2 = jasmine.createSpy('task2'),
|
||||
promise1, promise2;
|
||||
|
||||
promise1 = $timeout(task1);
|
||||
$timeout.flush();
|
||||
promise2 = $timeout(task2);
|
||||
|
||||
expect($timeout.cancel(promise1)).toBe(false);
|
||||
expect($timeout.cancel(promise2)).toBe(true);
|
||||
}));
|
||||
});
|
||||
});
|
||||
17
test/ngMock/angular-mocksSpec.js
vendored
17
test/ngMock/angular-mocksSpec.js
vendored
@@ -313,6 +313,23 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
describe('$timeout', function() {
|
||||
it('should expose flush method that will flush the pending queue of tasks', inject(
|
||||
function($timeout) {
|
||||
var logger = [],
|
||||
logFn = function(msg) { return function() { logger.push(msg) }};
|
||||
|
||||
$timeout(logFn('t1'));
|
||||
$timeout(logFn('t2'), 200);
|
||||
$timeout(logFn('t3'));
|
||||
expect(logger).toEqual([]);
|
||||
|
||||
$timeout.flush();
|
||||
expect(logger).toEqual(['t1', 't3', 't2']);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('angular.mock.dump', function(){
|
||||
var d = angular.mock.dump;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user