Skip to content

Commit b0a28d9

Browse files
committed
feat(*): implement more granular pending task tracking
Previously, all pending async tasks (tracked via `$browser`) are treated the same. I.e. things like `$$testability.whenStable()` and `ngMock#$timeout.verifyNoPendingTasks()` take all tasks into account. Yet, in some cases we might be interested in specific tasks only. For example, if one wants to verify there are no pending `$timeout`s, they don't care if there are other pending tasks, such as `$http` requests. Similarly, one might want to get notified when all `$http` requests have completed and does not care about pending promises. This commit adds support for more granular task tracking, by enabling callers to specify the type of task that is being added/removed from the queue and enabling listeners to be triggered when specific types of tasks are completed (even if there are more pending tasks of different types). The change is backwards compatible. I.e. calling the affected methods with no explicit task-type, behaves the same as before. Related to angular#14336.
1 parent aee7d53 commit b0a28d9

13 files changed

+373
-146
lines changed

src/ng/browser.js

+74-25
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,48 @@
2323
* @param {object} $sniffer $sniffer service
2424
*/
2525
function Browser(window, document, $log, $sniffer) {
26+
var ALL_TASKS_TYPE = '$$all$$',
27+
DEFAULT_TASK_TYPE = '$$default$$';
28+
2629
var self = this,
2730
location = window.location,
2831
history = window.history,
2932
setTimeout = window.setTimeout,
3033
clearTimeout = window.clearTimeout,
31-
pendingDeferIds = {};
34+
pendingDeferIds = {},
35+
outstandingRequestCounts = {},
36+
outstandingRequestCallbacks = [];
3237

3338
self.isMock = false;
3439

35-
var outstandingRequestCount = 0;
36-
var outstandingRequestCallbacks = [];
37-
3840
// TODO(vojta): remove this temporary api
3941
self.$$completeOutstandingRequest = completeOutstandingRequest;
40-
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
42+
self.$$incOutstandingRequestCount = incOutstandingRequestCount;
4143

4244
/**
43-
* Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
44-
* counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
45+
* Executes the `fn` function and decrements the appropriate `outstandingRequestCounts` counter.
46+
* If the counter reaches 0, all the corresponding `outstandingRequestCallbacks` are executed.
47+
* @param {Function} fn - The function to execute.
48+
* @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is being completed.
4549
*/
46-
function completeOutstandingRequest(fn) {
50+
function completeOutstandingRequest(fn, taskType) {
51+
taskType = taskType || DEFAULT_TASK_TYPE;
4752
try {
48-
fn.apply(null, sliceArgs(arguments, 1));
53+
fn();
4954
} finally {
50-
outstandingRequestCount--;
51-
if (outstandingRequestCount === 0) {
52-
while (outstandingRequestCallbacks.length) {
55+
decOutstandingRequestCount(taskType);
56+
57+
var countForType = outstandingRequestCounts[taskType];
58+
var countForAll = outstandingRequestCounts[ALL_TASKS_TYPE];
59+
60+
// If at least one of the queues (`ALL_TASKS_TYPE` or `taskType`) is empty, run callbacks.
61+
if (!countForAll || !countForType) {
62+
var getNextCallback = !countForAll ? getLastCallback : getLastCallbackForType;
63+
var nextCb;
64+
65+
while ((nextCb = getNextCallback(taskType))) {
5366
try {
54-
outstandingRequestCallbacks.pop()();
67+
nextCb();
5568
} catch (e) {
5669
$log.error(e);
5770
}
@@ -60,6 +73,33 @@ function Browser(window, document, $log, $sniffer) {
6073
}
6174
}
6275

76+
function decOutstandingRequestCount(taskType) {
77+
taskType = taskType || DEFAULT_TASK_TYPE;
78+
if (outstandingRequestCounts[taskType]) outstandingRequestCounts[taskType]--;
79+
if (outstandingRequestCounts[ALL_TASKS_TYPE]) outstandingRequestCounts[ALL_TASKS_TYPE]--;
80+
}
81+
82+
function incOutstandingRequestCount(taskType) {
83+
taskType = taskType || DEFAULT_TASK_TYPE;
84+
outstandingRequestCounts[taskType] = (outstandingRequestCounts[taskType] || 0) + 1;
85+
outstandingRequestCounts[ALL_TASKS_TYPE] = (outstandingRequestCounts[ALL_TASKS_TYPE] || 0) + 1;
86+
}
87+
88+
function getLastCallback() {
89+
var cbInfo = outstandingRequestCallbacks.pop();
90+
return cbInfo && cbInfo.cb;
91+
}
92+
93+
function getLastCallbackForType(taskType) {
94+
for (var i = outstandingRequestCallbacks.length - 1; i >= 0; --i) {
95+
var cbInfo = outstandingRequestCallbacks[i];
96+
if (cbInfo.type === taskType) {
97+
outstandingRequestCallbacks.splice(i, 1);
98+
return cbInfo.cb;
99+
}
100+
}
101+
}
102+
63103
function getHash(url) {
64104
var index = url.indexOf('#');
65105
return index === -1 ? '' : url.substr(index);
@@ -68,13 +108,15 @@ function Browser(window, document, $log, $sniffer) {
68108
/**
69109
* @private
70110
* TODO(vojta): prefix this method with $$ ?
71-
* @param {function()} callback Function that will be called when no outstanding request
111+
* @param {function()} callback Function that will be called when no outstanding request.
112+
* @param {string=} [taskType=ALL_TASKS_TYPE] The type of tasks that will be waited for.
72113
*/
73-
self.notifyWhenNoOutstandingRequests = function(callback) {
74-
if (outstandingRequestCount === 0) {
114+
self.notifyWhenNoOutstandingRequests = function(callback, taskType) {
115+
taskType = taskType || ALL_TASKS_TYPE;
116+
if (!outstandingRequestCounts[taskType]) {
75117
callback();
76118
} else {
77-
outstandingRequestCallbacks.push(callback);
119+
outstandingRequestCallbacks.push({type: taskType, cb: callback});
78120
}
79121
};
80122

@@ -307,7 +349,8 @@ function Browser(window, document, $log, $sniffer) {
307349
/**
308350
* @name $browser#defer
309351
* @param {function()} fn A function, who's execution should be deferred.
310-
* @param {number=} [delay=0] of milliseconds to defer the function execution.
352+
* @param {number=} [delay=0] Number of milliseconds to defer the function execution.
353+
* @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is deferred.
311354
* @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
312355
*
313356
* @description
@@ -318,14 +361,19 @@ function Browser(window, document, $log, $sniffer) {
318361
* via `$browser.defer.flush()`.
319362
*
320363
*/
321-
self.defer = function(fn, delay) {
364+
self.defer = function(fn, delay, taskType) {
322365
var timeoutId;
323-
outstandingRequestCount++;
366+
367+
delay = delay || 0;
368+
taskType = taskType || DEFAULT_TASK_TYPE;
369+
370+
incOutstandingRequestCount(taskType);
324371
timeoutId = setTimeout(function() {
325372
delete pendingDeferIds[timeoutId];
326-
completeOutstandingRequest(fn);
327-
}, delay || 0);
328-
pendingDeferIds[timeoutId] = true;
373+
completeOutstandingRequest(fn, taskType);
374+
}, delay);
375+
pendingDeferIds[timeoutId] = taskType;
376+
329377
return timeoutId;
330378
};
331379

@@ -341,10 +389,11 @@ function Browser(window, document, $log, $sniffer) {
341389
* canceled.
342390
*/
343391
self.defer.cancel = function(deferId) {
344-
if (pendingDeferIds[deferId]) {
392+
if (pendingDeferIds.hasOwnProperty(deferId)) {
393+
var taskType = pendingDeferIds[deferId];
345394
delete pendingDeferIds[deferId];
346395
clearTimeout(deferId);
347-
completeOutstandingRequest(noop);
396+
completeOutstandingRequest(noop, taskType);
348397
return true;
349398
}
350399
return false;

src/ng/http.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@ function $HttpProvider() {
10541054
config.paramSerializer = isString(config.paramSerializer) ?
10551055
$injector.get(config.paramSerializer) : config.paramSerializer;
10561056

1057-
$browser.$$incOutstandingRequestCount();
1057+
$browser.$$incOutstandingRequestCount('$http');
10581058

10591059
var requestInterceptors = [];
10601060
var responseInterceptors = [];
@@ -1092,7 +1092,7 @@ function $HttpProvider() {
10921092
}
10931093

10941094
function completeOutstandingRequest() {
1095-
$browser.$$completeOutstandingRequest(noop);
1095+
$browser.$$completeOutstandingRequest(noop, '$http');
10961096
}
10971097

10981098
function executeHeaderFns(headers, config) {

src/ng/rootScope.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1122,7 +1122,7 @@ function $RootScopeProvider() {
11221122
if (asyncQueue.length) {
11231123
$rootScope.$digest();
11241124
}
1125-
});
1125+
}, null, '$evalAsync');
11261126
}
11271127

11281128
asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
@@ -1493,7 +1493,7 @@ function $RootScopeProvider() {
14931493
if (applyAsyncId === null) {
14941494
applyAsyncId = $browser.defer(function() {
14951495
$rootScope.$apply(flushApplyAsync);
1496-
});
1496+
}, null, '$applyAsync');
14971497
}
14981498
}
14991499
}];

src/ng/testability.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,24 @@ function $$TestabilityProvider() {
104104
* @name $$testability#whenStable
105105
*
106106
* @description
107-
* Calls the callback when $timeout and $http requests are completed.
107+
* Calls the callback when all pending requests (such as `$timeout` and `$http`) are completed.
108+
* You can optionally pass a `taskType`, in which case the callback will be called when all
109+
* pending requests of that type are completed (even if there are tasks of different types still
110+
* pending).
111+
*
112+
* Available task types:
113+
* - `$timeout`: Pending timeouts (via {@link $timeout}).
114+
* - `$http`: Pending HTTP requests (via {@link $http}).
115+
* - `$route`: A route transition is in progress (via {@link $route}).
116+
* - `$applyAsync`: Pending tasks scheduled via {@link $rootScope#$applyAsync}.
117+
* - `$evalAsync`: Pending tasks scheduled via {@link $rootScope#$evalAsync}.
118+
* These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
108119
*
109120
* @param {function} callback
121+
* @param {string=} taskType
110122
*/
111-
testability.whenStable = function(callback) {
112-
$browser.notifyWhenNoOutstandingRequests(callback);
123+
testability.whenStable = function(callback, taskType) {
124+
$browser.notifyWhenNoOutstandingRequests(callback, taskType);
113125
};
114126

115127
return testability;

src/ng/timeout.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function $TimeoutProvider() {
6363
}
6464

6565
if (!skipApply) $rootScope.$apply();
66-
}, delay);
66+
}, delay, '$timeout');
6767

6868
promise.$$timeoutId = timeoutId;
6969
deferreds[timeoutId] = deferred;

0 commit comments

Comments
 (0)