-
Notifications
You must be signed in to change notification settings - Fork 27.4k
refactor($http): coalesce calls to $rootScope.$apply WIP #7634
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,7 +81,7 @@ function $RootScopeProvider(){ | |
|
||
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', | ||
function( $injector, $exceptionHandler, $parse, $browser) { | ||
|
||
var $timeout; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could not inject this directly, due to circular dependencies |
||
/** | ||
* @ngdoc type | ||
* @name $rootScope.Scope | ||
|
@@ -641,7 +641,7 @@ function $RootScopeProvider(){ | |
* ``` | ||
* | ||
*/ | ||
$digest: function() { | ||
$digest: function(calledByFn) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this argument is just a helper which enables a bit better support for mocking --- it could be removed and tests would need to be refactored |
||
var watch, value, last, | ||
watchers, | ||
asyncQueue = this.$$asyncQueue, | ||
|
@@ -972,6 +972,21 @@ function $RootScopeProvider(){ | |
} | ||
}, | ||
|
||
|
||
$applyAsync: function(timeout) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be $$? And |
||
if (!$timeout) { | ||
$timeout = $injector.get('$timeout'); | ||
} | ||
if (!isNumber(timeout)) { | ||
timeout = 0; | ||
} | ||
|
||
if (!applyingAsync) { | ||
applyingAsync = true; | ||
$timeout(applyAsync, timeout, false); | ||
} | ||
}, | ||
|
||
/** | ||
* @ngdoc method | ||
* @name $rootScope.Scope#$on | ||
|
@@ -1169,6 +1184,7 @@ function $RootScopeProvider(){ | |
}; | ||
|
||
var $rootScope = new Scope(); | ||
var applyingAsync = false; | ||
|
||
return $rootScope; | ||
|
||
|
@@ -1201,6 +1217,15 @@ function $RootScopeProvider(){ | |
} while ((current = current.$parent)); | ||
} | ||
|
||
function applyAsync() { | ||
applyingAsync = false; | ||
try { | ||
$rootScope.$digest("applyAsync"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: single quotes |
||
} catch (e) { | ||
$exceptionHandler(e); | ||
} | ||
} | ||
|
||
/** | ||
* function used as an initial value for watchers. | ||
* because it's unique we can easily tell it apart from other values | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1067,7 +1067,7 @@ angular.mock.dump = function(object) { | |
``` | ||
*/ | ||
angular.mock.$HttpBackendProvider = function() { | ||
this.$get = ['$rootScope', createHttpBackendMock]; | ||
this.$get = ['$rootScope', '$timeout', createHttpBackendMock]; | ||
}; | ||
|
||
/** | ||
|
@@ -1084,7 +1084,7 @@ angular.mock.$HttpBackendProvider = function() { | |
* @param {Object=} $browser Auto-flushing enabled if specified | ||
* @return {Object} Instance of $httpBackend mock | ||
*/ | ||
function createHttpBackendMock($rootScope, $delegate, $browser) { | ||
function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { | ||
var definitions = [], | ||
expectations = [], | ||
responses = [], | ||
|
@@ -1442,12 +1442,19 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { | |
* @param {number=} count Number of responses to flush (in the order they arrived). If undefined, | ||
* all pending requests will be flushed. If there are no pending requests when the flush method | ||
* is called an exception is thrown (as this typically a sign of programming error). | ||
* @param {number=|boolean} flushTimeout amount to pass to $timeout.flush(). If value is false, | ||
* $timeout.flush() is never called --- Otherwise, if the value is a number, it is called with | ||
* that value. Otherwise, it is called with Infinity. This enables applications to test their | ||
* behaviour using simulated coalesced $applyAsync calls. | ||
*/ | ||
$httpBackend.flush = function(count) { | ||
$httpBackend.flush = function(count, flushTimeout) { | ||
$rootScope.$digest(); | ||
if (!responses.length) throw new Error('No pending request to flush !'); | ||
if (!responses.length && !angular.isNumber(flushTimeout) && typeof flushTimeout !== "boolean") { | ||
throw new Error('No pending request to flush !'); | ||
} | ||
|
||
if (angular.isDefined(count)) { | ||
// flush the responses | ||
if (angular.isNumber(count)) { | ||
while (count--) { | ||
if (!responses.length) throw new Error('No more pending request to flush !'); | ||
responses.shift()(); | ||
|
@@ -1457,6 +1464,16 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { | |
responses.shift()(); | ||
} | ||
} | ||
|
||
// If asking to flush timeout, do so | ||
if (typeof $timeout.flush === 'function' && flushTimeout !== false) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know I could use angular.isFunction here, but it doesn't seem that ueful and wouldn't save that many characters |
||
if (typeof flushTimeout === 'number') { | ||
$timeout.flush(flushTimeout); | ||
} else { | ||
$timeout.flush(Infinity); | ||
} | ||
} | ||
|
||
$httpBackend.verifyNoOutstandingExpectation(); | ||
}; | ||
|
||
|
@@ -1627,6 +1644,20 @@ function MockXhr() { | |
this.abort = angular.noop; | ||
} | ||
|
||
angular.mock.$RootScopeDecorator = ['$delegate', '$browser', function ($delegate, $browser) { | ||
var Scope = $delegate.constructor; | ||
var digest = Scope.prototype.$digest; | ||
|
||
Scope.prototype.$digest = function(caller) { | ||
digest.call(this); | ||
if (caller === "applyAsync") { | ||
if (angular.isFunction(this.$$didAsyncDigest)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is the way the function parameter helps testing a little bit --- since $httpBackend.flush() needs to $digest a lot anyways, this is useful because it tells us when the "actual" digest happens --- but I'm not opposed to removing it if this can be refactored differently (I spent time thinking how to do this and it kept ending up sort of bad the other ways) |
||
this.$$didAsyncDigest(); | ||
} | ||
} | ||
}; | ||
return $delegate; | ||
}]; | ||
|
||
/** | ||
* @ngdoc service | ||
|
@@ -1752,6 +1783,7 @@ angular.module('ngMock', ['ng']).provider({ | |
$httpBackend: angular.mock.$HttpBackendProvider, | ||
$rootElement: angular.mock.$RootElementProvider | ||
}).config(['$provide', function($provide) { | ||
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); | ||
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator); | ||
$provide.decorator('$$rAF', angular.mock.$RAFDecorator); | ||
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator); | ||
|
@@ -1950,7 +1982,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { | |
*/ | ||
angular.mock.e2e = {}; | ||
angular.mock.e2e.$httpBackendDecorator = | ||
['$rootScope', '$delegate', '$browser', createHttpBackendMock]; | ||
['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock]; | ||
|
||
|
||
angular.mock.clearDataCache = function() { | ||
|
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.
Shouldn't be named like a constant