Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 84fb041

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 #14336.
1 parent aee7d53 commit 84fb041

13 files changed

+579
-194
lines changed

src/ng/browser.js

+76-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,35 @@ function Browser(window, document, $log, $sniffer) {
6073
}
6174
}
6275

76+
function decOutstandingRequestCount(taskType) {
77+
taskType = taskType || DEFAULT_TASK_TYPE;
78+
if (outstandingRequestCounts[taskType]) {
79+
outstandingRequestCounts[taskType]--;
80+
outstandingRequestCounts[ALL_TASKS_TYPE]--;
81+
}
82+
}
83+
84+
function incOutstandingRequestCount(taskType) {
85+
taskType = taskType || DEFAULT_TASK_TYPE;
86+
outstandingRequestCounts[taskType] = (outstandingRequestCounts[taskType] || 0) + 1;
87+
outstandingRequestCounts[ALL_TASKS_TYPE] = (outstandingRequestCounts[ALL_TASKS_TYPE] || 0) + 1;
88+
}
89+
90+
function getLastCallback() {
91+
var cbInfo = outstandingRequestCallbacks.pop();
92+
return cbInfo && cbInfo.cb;
93+
}
94+
95+
function getLastCallbackForType(taskType) {
96+
for (var i = outstandingRequestCallbacks.length - 1; i >= 0; --i) {
97+
var cbInfo = outstandingRequestCallbacks[i];
98+
if (cbInfo.type === taskType) {
99+
outstandingRequestCallbacks.splice(i, 1);
100+
return cbInfo.cb;
101+
}
102+
}
103+
}
104+
63105
function getHash(url) {
64106
var index = url.indexOf('#');
65107
return index === -1 ? '' : url.substr(index);
@@ -68,13 +110,15 @@ function Browser(window, document, $log, $sniffer) {
68110
/**
69111
* @private
70112
* TODO(vojta): prefix this method with $$ ?
71-
* @param {function()} callback Function that will be called when no outstanding request
113+
* @param {function()} callback Function that will be called when no outstanding request.
114+
* @param {string=} [taskType=ALL_TASKS_TYPE] The type of tasks that will be waited for.
72115
*/
73-
self.notifyWhenNoOutstandingRequests = function(callback) {
74-
if (outstandingRequestCount === 0) {
116+
self.notifyWhenNoOutstandingRequests = function(callback, taskType) {
117+
taskType = taskType || ALL_TASKS_TYPE;
118+
if (!outstandingRequestCounts[taskType]) {
75119
callback();
76120
} else {
77-
outstandingRequestCallbacks.push(callback);
121+
outstandingRequestCallbacks.push({type: taskType, cb: callback});
78122
}
79123
};
80124

@@ -307,7 +351,8 @@ function Browser(window, document, $log, $sniffer) {
307351
/**
308352
* @name $browser#defer
309353
* @param {function()} fn A function, who's execution should be deferred.
310-
* @param {number=} [delay=0] of milliseconds to defer the function execution.
354+
* @param {number=} [delay=0] Number of milliseconds to defer the function execution.
355+
* @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is deferred.
311356
* @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
312357
*
313358
* @description
@@ -318,14 +363,19 @@ function Browser(window, document, $log, $sniffer) {
318363
* via `$browser.defer.flush()`.
319364
*
320365
*/
321-
self.defer = function(fn, delay) {
366+
self.defer = function(fn, delay, taskType) {
322367
var timeoutId;
323-
outstandingRequestCount++;
368+
369+
delay = delay || 0;
370+
taskType = taskType || DEFAULT_TASK_TYPE;
371+
372+
incOutstandingRequestCount(taskType);
324373
timeoutId = setTimeout(function() {
325374
delete pendingDeferIds[timeoutId];
326-
completeOutstandingRequest(fn);
327-
}, delay || 0);
328-
pendingDeferIds[timeoutId] = true;
375+
completeOutstandingRequest(fn, taskType);
376+
}, delay);
377+
pendingDeferIds[timeoutId] = taskType;
378+
329379
return timeoutId;
330380
};
331381

@@ -341,10 +391,11 @@ function Browser(window, document, $log, $sniffer) {
341391
* canceled.
342392
*/
343393
self.defer.cancel = function(deferId) {
344-
if (pendingDeferIds[deferId]) {
394+
if (pendingDeferIds.hasOwnProperty(deferId)) {
395+
var taskType = pendingDeferIds[deferId];
345396
delete pendingDeferIds[deferId];
346397
clearTimeout(deferId);
347-
completeOutstandingRequest(noop);
398+
completeOutstandingRequest(noop, taskType);
348399
return true;
349400
}
350401
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

+9-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,15 @@ 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 tasks are completed.
108+
*
109+
* Types of tasks waited for include:
110+
* - Pending timeouts (via {@link $timeout}).
111+
* - Pending HTTP requests (via {@link $http}).
112+
* - In-progress route transitions (via {@link $route}).
113+
* - Pending tasks scheduled via {@link $rootScope#$applyAsync}.
114+
* - Pending tasks scheduled via {@link $rootScope#$evalAsync}.
115+
* These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
108116
*
109117
* @param {function} callback
110118
*/

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)