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

feat(*): implement more granular pending task tracking #16603

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions angularFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var angularFiles = {
'src/ng/httpBackend.js',
'src/ng/interpolate.js',
'src/ng/interval.js',
'src/ng/intervalFactory.js',
'src/ng/jsonpCallbacks.js',
'src/ng/locale.js',
'src/ng/location.js',
Expand All @@ -40,6 +41,7 @@ var angularFiles = {
'src/ng/sanitizeUri.js',
'src/ng/sce.js',
'src/ng/sniffer.js',
'src/ng/taskTrackerFactory.js',
'src/ng/templateRequest.js',
'src/ng/testability.js',
'src/ng/timeout.js',
Expand Down
4 changes: 4 additions & 0 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
$FilterProvider,
$$ForceReflowProvider,
$InterpolateProvider,
$$IntervalFactoryProvider,
$IntervalProvider,
$HttpProvider,
$HttpParamSerializerProvider,
Expand All @@ -88,6 +89,7 @@
$SceProvider,
$SceDelegateProvider,
$SnifferProvider,
$$TaskTrackerFactoryProvider,
$TemplateCacheProvider,
$TemplateRequestProvider,
$$TestabilityProvider,
Expand Down Expand Up @@ -241,6 +243,7 @@ function publishExternalAPI(angular) {
$$forceReflow: $$ForceReflowProvider,
$interpolate: $InterpolateProvider,
$interval: $IntervalProvider,
$$intervalFactory: $$IntervalFactoryProvider,
$http: $HttpProvider,
$httpParamSerializer: $HttpParamSerializerProvider,
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
Expand All @@ -256,6 +259,7 @@ function publishExternalAPI(angular) {
$sce: $SceProvider,
$sceDelegate: $SceDelegateProvider,
$sniffer: $SnifferProvider,
$$taskTrackerFactory: $$TaskTrackerFactoryProvider,
$templateCache: $TemplateCacheProvider,
$templateRequest: $TemplateRequestProvider,
$$testability: $$TestabilityProvider,
Expand Down
90 changes: 34 additions & 56 deletions src/ng/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,61 +22,27 @@
* @param {object} $log window.console or an object with the same interface.
* @param {object} $sniffer $sniffer service
*/
function Browser(window, document, $log, $sniffer) {
function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
var self = this,
location = window.location,
history = window.history,
setTimeout = window.setTimeout,
clearTimeout = window.clearTimeout,
pendingDeferIds = {};
pendingDeferIds = {},
taskTracker = $$taskTrackerFactory($log);

self.isMock = false;

var outstandingRequestCount = 0;
var outstandingRequestCallbacks = [];
//////////////////////////////////////////////////////////////
// Task-tracking API
//////////////////////////////////////////////////////////////

// TODO(vojta): remove this temporary api
self.$$completeOutstandingRequest = completeOutstandingRequest;
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };

/**
* Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
* counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
*/
function completeOutstandingRequest(fn) {
try {
fn.apply(null, sliceArgs(arguments, 1));
} finally {
outstandingRequestCount--;
if (outstandingRequestCount === 0) {
while (outstandingRequestCallbacks.length) {
try {
outstandingRequestCallbacks.pop()();
} catch (e) {
$log.error(e);
}
}
}
}
}

function getHash(url) {
var index = url.indexOf('#');
return index === -1 ? '' : url.substr(index);
}
self.$$completeOutstandingRequest = taskTracker.completeTask;
self.$$incOutstandingRequestCount = taskTracker.incTaskCount;

/**
* @private
* TODO(vojta): prefix this method with $$ ?
* @param {function()} callback Function that will be called when no outstanding request
*/
self.notifyWhenNoOutstandingRequests = function(callback) {
if (outstandingRequestCount === 0) {
callback();
} else {
outstandingRequestCallbacks.push(callback);
}
};
// TODO(vojta): prefix this method with $$ ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably a bit late for this TODO now :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope never dies 😃

self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;

//////////////////////////////////////////////////////////////
// URL API
Expand All @@ -96,6 +62,11 @@ function Browser(window, document, $log, $sniffer) {

cacheState();

function getHash(url) {
var index = url.indexOf('#');
return index === -1 ? '' : url.substr(index);
}

/**
* @name $browser#url
*
Expand Down Expand Up @@ -307,7 +278,8 @@ function Browser(window, document, $log, $sniffer) {
/**
* @name $browser#defer
* @param {function()} fn A function, who's execution should be deferred.
* @param {number=} [delay=0] of milliseconds to defer the function execution.
* @param {number=} [delay=0] Number of milliseconds to defer the function execution.
* @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is deferred.
* @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
*
* @description
Expand All @@ -318,14 +290,19 @@ function Browser(window, document, $log, $sniffer) {
* via `$browser.defer.flush()`.
*
*/
self.defer = function(fn, delay) {
self.defer = function(fn, delay, taskType) {
var timeoutId;
outstandingRequestCount++;

delay = delay || 0;
taskType = taskType || taskTracker.DEFAULT_TASK_TYPE;

taskTracker.incTaskCount(taskType);
timeoutId = setTimeout(function() {
delete pendingDeferIds[timeoutId];
completeOutstandingRequest(fn);
}, delay || 0);
pendingDeferIds[timeoutId] = true;
taskTracker.completeTask(fn, taskType);
}, delay);
pendingDeferIds[timeoutId] = taskType;

return timeoutId;
};

Expand All @@ -341,10 +318,11 @@ function Browser(window, document, $log, $sniffer) {
* canceled.
*/
self.defer.cancel = function(deferId) {
if (pendingDeferIds[deferId]) {
if (pendingDeferIds.hasOwnProperty(deferId)) {
var taskType = pendingDeferIds[deferId];
delete pendingDeferIds[deferId];
clearTimeout(deferId);
completeOutstandingRequest(noop);
taskTracker.completeTask(noop, taskType);
return true;
}
return false;
Expand All @@ -354,8 +332,8 @@ function Browser(window, document, $log, $sniffer) {

/** @this */
function $BrowserProvider() {
this.$get = ['$window', '$log', '$sniffer', '$document',
function($window, $log, $sniffer, $document) {
return new Browser($window, $document, $log, $sniffer);
}];
this.$get = ['$window', '$log', '$sniffer', '$document', '$$taskTrackerFactory',
function($window, $log, $sniffer, $document, $$taskTrackerFactory) {
return new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory);
}];
}
4 changes: 2 additions & 2 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ function $HttpProvider() {
config.paramSerializer = isString(config.paramSerializer) ?
$injector.get(config.paramSerializer) : config.paramSerializer;

$browser.$$incOutstandingRequestCount();
$browser.$$incOutstandingRequestCount('$http');

var requestInterceptors = [];
var responseInterceptors = [];
Expand Down Expand Up @@ -1092,7 +1092,7 @@ function $HttpProvider() {
}

function completeOutstandingRequest() {
$browser.$$completeOutstandingRequest(noop);
$browser.$$completeOutstandingRequest(noop, '$http');
}

function executeHeaderFns(headers, config) {
Expand Down
61 changes: 13 additions & 48 deletions src/ng/interval.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@ var $intervalMinErr = minErr('$interval');

/** @this */
function $IntervalProvider() {
this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
function($rootScope, $window, $q, $$q, $browser) {
this.$get = ['$$intervalFactory', '$window',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume we don't care that this is "breaking" the decoration of $interval by including a private service?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it break it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you ask ... doesn't look like it breaks anything (and it relied on a private service before)

function($$intervalFactory, $window) {
var intervals = {};

var setIntervalFn = function(tick, delay, deferred) {
var id = $window.setInterval(tick, delay);
intervals[id] = deferred;
return id;
};
var clearIntervalFn = function(id) {
$window.clearInterval(id);
delete intervals[id];
};

/**
* @ngdoc service
Expand Down Expand Up @@ -135,49 +143,7 @@ function $IntervalProvider() {
* </file>
* </example>
*/
function interval(fn, delay, count, invokeApply) {
var hasParams = arguments.length > 4,
args = hasParams ? sliceArgs(arguments, 4) : [],
setInterval = $window.setInterval,
clearInterval = $window.clearInterval,
iteration = 0,
skipApply = (isDefined(invokeApply) && !invokeApply),
deferred = (skipApply ? $$q : $q).defer(),
promise = deferred.promise;

count = isDefined(count) ? count : 0;

promise.$$intervalId = setInterval(function tick() {
if (skipApply) {
$browser.defer(callback);
} else {
$rootScope.$evalAsync(callback);
}
deferred.notify(iteration++);

if (count > 0 && iteration >= count) {
deferred.resolve(iteration);
clearInterval(promise.$$intervalId);
delete intervals[promise.$$intervalId];
}

if (!skipApply) $rootScope.$apply();

}, delay);

intervals[promise.$$intervalId] = deferred;

return promise;

function callback() {
if (!hasParams) {
fn(iteration);
} else {
fn.apply(null, args);
}
}
}

var interval = $$intervalFactory(setIntervalFn, clearIntervalFn);

/**
* @ngdoc method
Expand Down Expand Up @@ -205,8 +171,7 @@ function $IntervalProvider() {
// Interval cancels should not report an unhandled promise.
markQExceptionHandled(deferred.promise);
deferred.reject('canceled');
$window.clearInterval(id);
delete intervals[id];
clearIntervalFn(id);

return true;
};
Expand Down
48 changes: 48 additions & 0 deletions src/ng/intervalFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

/** @this */
function $$IntervalFactoryProvider() {
this.$get = ['$browser', '$q', '$$q', '$rootScope',
function($browser, $q, $$q, $rootScope) {
return function intervalFactory(setIntervalFn, clearIntervalFn) {
return function intervalFn(fn, delay, count, invokeApply) {
var hasParams = arguments.length > 4,
args = hasParams ? sliceArgs(arguments, 4) : [],
iteration = 0,
skipApply = isDefined(invokeApply) && !invokeApply,
deferred = (skipApply ? $$q : $q).defer(),
promise = deferred.promise;

count = isDefined(count) ? count : 0;

function callback() {
if (!hasParams) {
fn(iteration);
} else {
fn.apply(null, args);
}
}

function tick() {
if (skipApply) {
$browser.defer(callback);
} else {
$rootScope.$evalAsync(callback);
}
deferred.notify(iteration++);

if (count > 0 && iteration >= count) {
deferred.resolve(iteration);
clearIntervalFn(promise.$$intervalId);
}

if (!skipApply) $rootScope.$apply();
}

promise.$$intervalId = setIntervalFn(tick, delay, deferred, skipApply);

return promise;
};
};
}];
}
4 changes: 2 additions & 2 deletions src/ng/rootScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -1122,7 +1122,7 @@ function $RootScopeProvider() {
if (asyncQueue.length) {
$rootScope.$digest();
}
});
}, null, '$evalAsync');
}

asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
Expand Down Expand Up @@ -1493,7 +1493,7 @@ function $RootScopeProvider() {
if (applyAsyncId === null) {
applyAsyncId = $browser.defer(function() {
$rootScope.$apply(flushApplyAsync);
});
}, null, '$applyAsync');
}
}
}];
Expand Down
Loading