Skip to content

Commit a015177

Browse files
committed
refactor($browser): share task-tracking code between $browser and ngMock/$browser
This avoids code/logic duplication and helps the implementations stay in-sync.
1 parent 4f7b4b6 commit a015177

File tree

7 files changed

+178
-181
lines changed

7 files changed

+178
-181
lines changed

angularFiles.js

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var angularFiles = {
4141
'src/ng/sanitizeUri.js',
4242
'src/ng/sce.js',
4343
'src/ng/sniffer.js',
44+
'src/ng/taskTrackerFactory.js',
4445
'src/ng/templateRequest.js',
4546
'src/ng/testability.js',
4647
'src/ng/timeout.js',

src/AngularPublic.js

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
$SceProvider,
9090
$SceDelegateProvider,
9191
$SnifferProvider,
92+
$$TaskTrackerFactoryProvider,
9293
$TemplateCacheProvider,
9394
$TemplateRequestProvider,
9495
$$TestabilityProvider,
@@ -258,6 +259,7 @@ function publishExternalAPI(angular) {
258259
$sce: $SceProvider,
259260
$sceDelegate: $SceDelegateProvider,
260261
$sniffer: $SnifferProvider,
262+
$$taskTrackerFactory: $$TaskTrackerFactoryProvider,
261263
$templateCache: $TemplateCacheProvider,
262264
$templateRequest: $TemplateRequestProvider,
263265
$$testability: $$TestabilityProvider,

src/ng/browser.js

+23-96
Original file line numberDiff line numberDiff line change
@@ -22,105 +22,27 @@
2222
* @param {object} $log window.console or an object with the same interface.
2323
* @param {object} $sniffer $sniffer service
2424
*/
25-
function Browser(window, document, $log, $sniffer) {
26-
var ALL_TASKS_TYPE = '$$all$$',
27-
DEFAULT_TASK_TYPE = '$$default$$';
28-
25+
function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
2926
var self = this,
3027
location = window.location,
3128
history = window.history,
3229
setTimeout = window.setTimeout,
3330
clearTimeout = window.clearTimeout,
3431
pendingDeferIds = {},
35-
outstandingRequestCounts = {},
36-
outstandingRequestCallbacks = [];
32+
taskTracker = $$taskTrackerFactory($log);
3733

3834
self.isMock = false;
3935

40-
// TODO(vojta): remove this temporary api
41-
self.$$completeOutstandingRequest = completeOutstandingRequest;
42-
self.$$incOutstandingRequestCount = incOutstandingRequestCount;
43-
44-
/**
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.
49-
*/
50-
function completeOutstandingRequest(fn, taskType) {
51-
taskType = taskType || DEFAULT_TASK_TYPE;
52-
try {
53-
fn();
54-
} finally {
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))) {
66-
try {
67-
nextCb();
68-
} catch (e) {
69-
$log.error(e);
70-
}
71-
}
72-
}
73-
}
74-
}
75-
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-
}
36+
//////////////////////////////////////////////////////////////
37+
// Task-tracking API
38+
//////////////////////////////////////////////////////////////
10439

105-
function getHash(url) {
106-
var index = url.indexOf('#');
107-
return index === -1 ? '' : url.substr(index);
108-
}
40+
// TODO(vojta): remove this temporary api
41+
self.$$completeOutstandingRequest = taskTracker.completeTask;
42+
self.$$incOutstandingRequestCount = taskTracker.incTaskCount;
10943

110-
/**
111-
* @private
112-
* TODO(vojta): prefix this method with $$ ?
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.
115-
*/
116-
self.notifyWhenNoOutstandingRequests = function(callback, taskType) {
117-
taskType = taskType || ALL_TASKS_TYPE;
118-
if (!outstandingRequestCounts[taskType]) {
119-
callback();
120-
} else {
121-
outstandingRequestCallbacks.push({type: taskType, cb: callback});
122-
}
123-
};
44+
// TODO(vojta): prefix this method with $$ ?
45+
self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;
12446

12547
//////////////////////////////////////////////////////////////
12648
// URL API
@@ -140,6 +62,11 @@ function Browser(window, document, $log, $sniffer) {
14062

14163
cacheState();
14264

65+
function getHash(url) {
66+
var index = url.indexOf('#');
67+
return index === -1 ? '' : url.substr(index);
68+
}
69+
14370
/**
14471
* @name $browser#url
14572
*
@@ -367,12 +294,12 @@ function Browser(window, document, $log, $sniffer) {
367294
var timeoutId;
368295

369296
delay = delay || 0;
370-
taskType = taskType || DEFAULT_TASK_TYPE;
297+
taskType = taskType || taskTracker.DEFAULT_TASK_TYPE;
371298

372-
incOutstandingRequestCount(taskType);
299+
taskTracker.incTaskCount(taskType);
373300
timeoutId = setTimeout(function() {
374301
delete pendingDeferIds[timeoutId];
375-
completeOutstandingRequest(fn, taskType);
302+
taskTracker.completeTask(fn, taskType);
376303
}, delay);
377304
pendingDeferIds[timeoutId] = taskType;
378305

@@ -395,7 +322,7 @@ function Browser(window, document, $log, $sniffer) {
395322
var taskType = pendingDeferIds[deferId];
396323
delete pendingDeferIds[deferId];
397324
clearTimeout(deferId);
398-
completeOutstandingRequest(noop, taskType);
325+
taskTracker.completeTask(noop, taskType);
399326
return true;
400327
}
401328
return false;
@@ -405,8 +332,8 @@ function Browser(window, document, $log, $sniffer) {
405332

406333
/** @this */
407334
function $BrowserProvider() {
408-
this.$get = ['$window', '$log', '$sniffer', '$document',
409-
function($window, $log, $sniffer, $document) {
410-
return new Browser($window, $document, $log, $sniffer);
411-
}];
335+
this.$get = ['$window', '$log', '$sniffer', '$document', '$$taskTrackerFactory',
336+
function($window, $log, $sniffer, $document, $$taskTrackerFactory) {
337+
return new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory);
338+
}];
412339
}

src/ng/taskTrackerFactory.js

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
'use strict';
2+
3+
/**
4+
* ! This is a private undocumented service !
5+
*
6+
* @name $$taskTrackerFactory
7+
* @description
8+
* A function to create `TaskTracker` instances.
9+
*
10+
* A `TaskTracker` can keep track of pending tasks (grouped by type) and can notify interested
11+
* parties when all pending tasks (or tasks of a specific type) have been completed.
12+
*
13+
* @param {$log} log - A logger instance (such as `$log`). Used to log error during callback
14+
* execution.
15+
*
16+
* @this
17+
*/
18+
function $$TaskTrackerFactoryProvider() {
19+
this.$get = valueFn(function(log) { return new TaskTracker(log); });
20+
}
21+
22+
function TaskTracker(log) {
23+
var self = this;
24+
var taskCounts = {};
25+
var taskCallbacks = [];
26+
27+
var ALL_TASKS_TYPE = self.ALL_TASKS_TYPE = '$$all$$';
28+
var DEFAULT_TASK_TYPE = self.DEFAULT_TASK_TYPE = '$$default$$';
29+
30+
/**
31+
* Execute the specified function and decrement the appropriate `taskCounts` counter.
32+
* If the counter reaches 0, all corresponding `taskCallbacks` are executed.
33+
*
34+
* @param {Function} fn - The function to execute.
35+
* @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task that is being completed.
36+
*/
37+
self.completeTask = completeTask;
38+
39+
/**
40+
* Increase the task count for the specified task type (or the default task type if non is
41+
* specified).
42+
*
43+
* @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task whose count will be increased.
44+
*/
45+
self.incTaskCount = incTaskCount;
46+
47+
/**
48+
* Execute the specified callback when all pending tasks have been completed.
49+
*
50+
* If there are no pending tasks, the callback is executed immediatelly. You can optionally limit
51+
* the tasks that will be waited for to a specific type, by passing a `taskType`.
52+
*
53+
* @param {function} callback - The function to call when there are no pending tasks.
54+
* @param {string=} [taskType=ALL_TASKS_TYPE] - The type of tasks that will be waited for.
55+
*/
56+
self.notifyWhenNoPendingTasks = notifyWhenNoPendingTasks;
57+
58+
function completeTask(fn, taskType) {
59+
taskType = taskType || DEFAULT_TASK_TYPE;
60+
61+
try {
62+
fn();
63+
} finally {
64+
decTaskCount(taskType);
65+
66+
var countForType = taskCounts[taskType];
67+
var countForAll = taskCounts[ALL_TASKS_TYPE];
68+
69+
// If at least one of the queues (`ALL_TASKS_TYPE` or `taskType`) is empty, run callbacks.
70+
if (!countForAll || !countForType) {
71+
var getNextCallback = !countForAll ? getLastCallback : getLastCallbackForType;
72+
var nextCb;
73+
74+
while ((nextCb = getNextCallback(taskType))) {
75+
try {
76+
nextCb();
77+
} catch (e) {
78+
log.error(e);
79+
}
80+
}
81+
}
82+
}
83+
}
84+
85+
function decTaskCount(taskType) {
86+
taskType = taskType || DEFAULT_TASK_TYPE;
87+
if (taskCounts[taskType]) {
88+
taskCounts[taskType]--;
89+
taskCounts[ALL_TASKS_TYPE]--;
90+
}
91+
}
92+
93+
function getLastCallback() {
94+
var cbInfo = taskCallbacks.pop();
95+
return cbInfo && cbInfo.cb;
96+
}
97+
98+
function getLastCallbackForType(taskType) {
99+
for (var i = taskCallbacks.length - 1; i >= 0; --i) {
100+
var cbInfo = taskCallbacks[i];
101+
if (cbInfo.type === taskType) {
102+
taskCallbacks.splice(i, 1);
103+
return cbInfo.cb;
104+
}
105+
}
106+
}
107+
108+
function incTaskCount(taskType) {
109+
taskType = taskType || DEFAULT_TASK_TYPE;
110+
taskCounts[taskType] = (taskCounts[taskType] || 0) + 1;
111+
taskCounts[ALL_TASKS_TYPE] = (taskCounts[ALL_TASKS_TYPE] || 0) + 1;
112+
}
113+
114+
function notifyWhenNoPendingTasks(callback, taskType) {
115+
taskType = taskType || ALL_TASKS_TYPE;
116+
if (!taskCounts[taskType]) {
117+
callback();
118+
} else {
119+
taskCallbacks.push({type: taskType, cb: callback});
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)