diff --git a/src/ng/timeout.js b/src/ng/timeout.js index 51e627b9b441..df389eed30bb 100644 --- a/src/ng/timeout.js +++ b/src/ng/timeout.js @@ -25,10 +25,32 @@ function $TimeoutProvider() { * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to * synchronously flush the queue of deferred functions. * + * You can also use `$timeout` to debounce the call of a function using the returned promise + * as the fourth parameter in the next call. See the following example: + * + *
+      *   var debounce;
+      *   var doRealSave = function() {
+      *      // Save model to DB
+      *   }
+      *   $scope.save = function() {
+      *      // debounce call for 2 seconds
+      *      debounce = $timeout(doRealSave, 2000, false, debounce);
+      *   }
+      * 
+ * + * And in the form: + * + *
+      *   
+      * 
+ * * @param {function()} fn A function, whose 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 ng.$rootScope.Scope#methods_$apply $apply} block. + * @param {Promise=} debounce If set to an outgoing promise, it will reject it before creating + * the new one. This allows debouncing the execution of the function. * @returns {Promise} 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. * @@ -120,12 +142,18 @@ function $TimeoutProvider() { */ - function timeout(fn, delay, invokeApply) { + function timeout(fn, delay, invokeApply, debounce) { var deferred = $q.defer(), promise = deferred.promise, skipApply = (isDefined(invokeApply) && !invokeApply), timeoutId; + // debouncing support + if (debounce && debounce.$$timeoutId in deferreds) { + deferreds[debounce.$$timeoutId].reject('debounced'); + $browser.defer.cancel(debounce.$$timeoutId); + } + timeoutId = $browser.defer(function() { try { deferred.resolve(fn()); diff --git a/test/ng/timeoutSpec.js b/test/ng/timeoutSpec.js index 97c8448eedce..6747865cc2b7 100644 --- a/test/ng/timeoutSpec.js +++ b/test/ng/timeoutSpec.js @@ -213,4 +213,34 @@ describe('$timeout', function() { expect(cancelSpy).toHaveBeenCalledOnce(); })); }); + + + describe('debouncing', function() { + it('should allow debouncing tasks', inject(function($timeout) { + var task = jasmine.createSpy('task'), + successtask = jasmine.createSpy('successtask'), + errortask = jasmine.createSpy('errortask'), + promise = null; + + promise = $timeout(task, 10000, true, promise); + promise.then(successtask, errortask); + + expect(task).not.toHaveBeenCalled(); + expect(successtask).not.toHaveBeenCalled(); + expect(errortask).not.toHaveBeenCalled(); + + promise = $timeout(task, 10000, true, promise); + expect(task).not.toHaveBeenCalled(); + expect(successtask).not.toHaveBeenCalled(); + expect(errortask).not.toHaveBeenCalled(); + + $timeout.flush(); + + expect(task).toHaveBeenCalled(); + // it's a different promise, so 'successtask' should not be called but 'errortask' should + expect(successtask).not.toHaveBeenCalled(); + expect(errortask).toHaveBeenCalled(); + })); + + }); });