azure-devops-ui
Version:
React components for building web UI in Azure DevOps
319 lines (318 loc) • 12.1 kB
JavaScript
/**
* The TimerManagement class is used to track a set of timers.
*/
export class TimerManagement {
constructor(parent) {
this.disposed = false;
this.immediateIds = null;
this.intervals = [];
this.timeouts = [];
this.parent = parent || null;
}
/**
* clearAllTimers is used to clear any active timers in the object.
*/
clearAllTimers() {
for (const intervalId of this.intervals) {
window.clearInterval(intervalId);
}
for (const timeoutId of this.timeouts) {
window.clearTimeout(timeoutId);
}
this.intervals.splice(0, this.intervals.length);
this.timeouts.splice(0, this.timeouts.length);
}
/**
* Clears the immediate.
* @param id - Id to cancel.
*/
clearImmediate(id) {
if (this.immediateIds && this.immediateIds[id]) {
window.clearTimeout(id);
delete this.immediateIds[id];
}
}
/**
* clearInterval is used to stop the series of callbacks that was setup through setInterval.
*
* @param intervalId - The id returned from eh setInterval call that you want stopped.
*/
clearInterval(intervalId) {
window.clearInterval(intervalId);
this.removeInterval(intervalId);
}
/**
* clearTimeout is used to stop a timeout callback that was setup through setTimeout.
*
* @param timeoutId - The id returned from the setTimeout call that you want stopped.
*/
clearTimeout(timeoutId) {
window.clearTimeout(timeoutId);
this.removeTimeout(timeoutId);
}
/**
* SetImmediate override, which will auto cancel the immediate during dispose.
* @param callback - Callback to execute.
* @returns The setTimeout id.
*/
setImmediate(callback) {
let immediateId = 0;
if (!this.disposed) {
if (!this.immediateIds) {
this.immediateIds = {};
}
const setImmediateCallback = () => {
// Time to execute the timeout, enqueue it as a foreground task to be executed.
try {
// Now delete the record and call the callback.
if (this.immediateIds) {
delete this.immediateIds[immediateId];
}
callback.apply(this.parent);
}
catch (e) { }
};
immediateId = window.setTimeout(setImmediateCallback, 0);
this.immediateIds[immediateId] = true;
}
return immediateId;
}
/**
* setInterval is used to setup a callback that is called on an interval.
*
* @param callback - The callback that should be called each interval time period.
*
* @param milliseconds - The number of milliseconds between each callback.
*
* @param args - Optional variable argument list passed to the callback.
*
* @returns - returns a handle to the interval, this can be used to cancel through clearInterval method.
*/
setInterval(callback, milliseconds, ...args) {
// Create the timer, and add a method to track the completion so we can
// remove our tracked reference.
const intervalId = window.setInterval(callback, milliseconds, ...args);
this.intervals.push(intervalId);
return intervalId;
}
/**
* setTimeout is used to setup a onetime callback that is called after the specified timeout.
*
* @param callback - The callback that should be called when the time period has elapsed.
*
* @param milliseconds - The number of milliseconds before the callback should be called.
* Even if a timeout of 0 is used the callback will be executed asynchronouly.
*
* @param args - Optional variable argument list passed to the callback.
*
* @returns - returns a handle to the timeout, this can be used to cancel through clearTimeout method.
*/
setTimeout(callback, milliseconds, ...args) {
let timeoutId = 0;
// Create the timer, and add a method to track the completion so we can
// remove our tracked reference.
timeoutId = window.setTimeout(() => {
this.removeTimeout(timeoutId);
callback(...args);
}, milliseconds, ...args);
this.timeouts.push(timeoutId);
return timeoutId;
}
dispose() {
this.disposed = true;
this.parent = null;
this.clearAllTimers();
// Clear immediates.
if (this.immediateIds) {
for (const id in this.immediateIds) {
if (this.immediateIds.hasOwnProperty(id)) {
this.clearImmediate(parseInt(id, 10));
}
}
}
this.immediateIds = null;
}
/**
* Creates a function that will delay the execution of func until after wait milliseconds have
* elapsed since the last time it was invoked. Provide an options object to indicate that func
* should be invoked on the leading and/or trailing edge of the wait timeout. Subsequent calls
* to the debounced function will return the result of the last func call.
*
* Note: If leading and trailing options are true func will be called on the trailing edge of
* the timeout only if the the debounced function is invoked more than once during the wait
* timeout.
*
* @param func - The function to debounce.
* @param wait - The number of milliseconds to delay.
* @param options - The options object.
* @returns The new debounced function.
*/
debounce(func, wait, options) {
if (this.disposed) {
const noOpFunction = (() => {
/** Do nothing */
});
noOpFunction.cancel = () => {
return;
};
noOpFunction.flush = (() => null);
noOpFunction.pending = () => false;
return noOpFunction;
}
const waitMS = wait || 0;
let leading = false;
let trailing = true;
let maxWait = null;
let lastCallTime = 0;
let lastExecuteTime = new Date().getTime();
let lastResult;
let lastArgs;
let timeoutId = null;
if (options) {
leading = options.leading || false;
trailing = options.trailing || true;
maxWait = options.maxWait || null;
}
const markExecuted = (time) => {
if (timeoutId) {
this.clearTimeout(timeoutId);
timeoutId = null;
}
lastExecuteTime = time;
};
const invokeFunction = (time) => {
markExecuted(time);
lastResult = func.apply(null, lastArgs);
};
const callback = (userCall) => {
const now = new Date().getTime();
let executeImmediately = false;
if (userCall) {
if (leading && now - lastCallTime >= waitMS) {
executeImmediately = true;
}
lastCallTime = now;
}
const delta = now - lastCallTime;
let waitLength = waitMS - delta;
const maxWaitDelta = now - lastExecuteTime;
let maxWaitExpired = false;
if (maxWait !== null) {
// maxWait only matters when there is a pending callback
if (maxWaitDelta >= maxWait && timeoutId) {
maxWaitExpired = true;
}
else {
waitLength = Math.min(waitLength, maxWait - maxWaitDelta);
}
}
if (delta >= waitMS || maxWaitExpired || executeImmediately) {
invokeFunction(now);
}
else if ((timeoutId === null || !userCall) && trailing) {
timeoutId = this.setTimeout(callback, waitLength);
}
return lastResult;
};
const pending = () => {
return !!timeoutId;
};
const cancel = () => {
if (pending()) {
// Mark the debounced function as having executed
markExecuted(new Date().getTime());
}
};
const flush = () => {
if (pending()) {
invokeFunction(new Date().getTime());
}
return lastResult;
};
// tslint:disable-next-line:no-any
const resultFunction = ((...args) => {
lastArgs = args;
return callback(true);
});
resultFunction.cancel = cancel;
resultFunction.flush = flush;
resultFunction.pending = pending;
return resultFunction;
}
/**
* Creates a function that, when executed, will only call the func function at most once per
* every wait milliseconds. Provide an options object to indicate that func should be invoked
* on the leading and/or trailing edge of the wait timeout. Subsequent calls to the throttled
* function will return the result of the last func call.
*
* Note: If leading and trailing options are true func will be called on the trailing edge of
* the timeout only if the the throttled function is invoked more than once during the wait timeout.
*
* @param func - The function to throttle.
* @param wait - The number of milliseconds to throttle executions to. Defaults to 0.
* @param options - The options object.
* @returns The new throttled function.
*/
throttle(func, wait, options) {
if (this.disposed) {
const noOpFunction = (() => {
/** Do nothing */
});
noOpFunction.cancel = () => {
return;
};
noOpFunction.flush = (() => null);
noOpFunction.pending = () => false;
return noOpFunction;
}
const waitMS = wait || 0;
let leading = true;
let trailing = true;
let lastExecuteTime = 0;
let lastResult;
// tslint:disable-next-line:no-any
let lastArgs;
let timeoutId = null;
if (options && typeof options.leading === "boolean") {
leading = options.leading;
}
if (options && typeof options.trailing === "boolean") {
trailing = options.trailing;
}
const callback = (userCall) => {
const now = new Date().getTime();
const delta = now - lastExecuteTime;
const waitLength = leading ? waitMS - delta : waitMS;
if (delta >= waitMS && (!userCall || leading)) {
lastExecuteTime = now;
if (timeoutId) {
this.clearTimeout(timeoutId);
timeoutId = null;
}
lastResult = func.apply(null, lastArgs);
}
else if (timeoutId === null && trailing) {
timeoutId = this.setTimeout(callback, waitLength);
}
return lastResult;
};
// tslint:disable-next-line:no-any
const resultFunction = (...args) => {
lastArgs = args;
return callback(true);
};
return resultFunction;
}
removeInterval(intervalId) {
const index = this.intervals.indexOf(intervalId);
if (index >= 0) {
this.intervals.splice(index, 1);
}
}
removeTimeout(timeoutId) {
const index = this.timeouts.indexOf(timeoutId);
if (index >= 0) {
this.timeouts.splice(index, 1);
}
}
}