416 lines
17 KiB
JavaScript
416 lines
17 KiB
JavaScript
|
define(["require", "exports", "./dom/getWindow"], function (require, exports, getWindow_1) {
|
||
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.Async = void 0;
|
||
|
/**
|
||
|
* Bugs often appear in async code when stuff gets disposed, but async operations don't get canceled.
|
||
|
* This Async helper class solves these issues by tying async code to the lifetime of a disposable object.
|
||
|
*
|
||
|
* Usage: Anything class extending from BaseModel can access this helper via this.async. Otherwise create a
|
||
|
* new instance of the class and remember to call dispose() during your code's dispose handler.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
var Async = /** @class */ (function () {
|
||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
function Async(parent, onError) {
|
||
|
this._timeoutIds = null;
|
||
|
this._immediateIds = null;
|
||
|
this._intervalIds = null;
|
||
|
this._animationFrameIds = null;
|
||
|
this._isDisposed = false;
|
||
|
this._parent = parent || null;
|
||
|
this._onErrorHandler = onError;
|
||
|
this._noop = function () {
|
||
|
/* do nothing */
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* Dispose function, clears all async operations.
|
||
|
*/
|
||
|
Async.prototype.dispose = function () {
|
||
|
var id;
|
||
|
this._isDisposed = true;
|
||
|
this._parent = null;
|
||
|
// Clear timeouts.
|
||
|
if (this._timeoutIds) {
|
||
|
for (id in this._timeoutIds) {
|
||
|
if (this._timeoutIds.hasOwnProperty(id)) {
|
||
|
this.clearTimeout(parseInt(id, 10));
|
||
|
}
|
||
|
}
|
||
|
this._timeoutIds = null;
|
||
|
}
|
||
|
// Clear immediates.
|
||
|
if (this._immediateIds) {
|
||
|
for (id in this._immediateIds) {
|
||
|
if (this._immediateIds.hasOwnProperty(id)) {
|
||
|
this.clearImmediate(parseInt(id, 10));
|
||
|
}
|
||
|
}
|
||
|
this._immediateIds = null;
|
||
|
}
|
||
|
// Clear intervals.
|
||
|
if (this._intervalIds) {
|
||
|
for (id in this._intervalIds) {
|
||
|
if (this._intervalIds.hasOwnProperty(id)) {
|
||
|
this.clearInterval(parseInt(id, 10));
|
||
|
}
|
||
|
}
|
||
|
this._intervalIds = null;
|
||
|
}
|
||
|
// Clear animation frames.
|
||
|
if (this._animationFrameIds) {
|
||
|
for (id in this._animationFrameIds) {
|
||
|
if (this._animationFrameIds.hasOwnProperty(id)) {
|
||
|
this.cancelAnimationFrame(parseInt(id, 10));
|
||
|
}
|
||
|
}
|
||
|
this._animationFrameIds = null;
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* SetTimeout override, which will auto cancel the timeout during dispose.
|
||
|
* @param callback - Callback to execute.
|
||
|
* @param duration - Duration in milliseconds.
|
||
|
* @returns The setTimeout id.
|
||
|
*/
|
||
|
Async.prototype.setTimeout = function (callback, duration) {
|
||
|
var _this = this;
|
||
|
var timeoutId = 0;
|
||
|
if (!this._isDisposed) {
|
||
|
if (!this._timeoutIds) {
|
||
|
this._timeoutIds = {};
|
||
|
}
|
||
|
timeoutId = setTimeout(function () {
|
||
|
// 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._timeoutIds) {
|
||
|
delete _this._timeoutIds[timeoutId];
|
||
|
}
|
||
|
callback.apply(_this._parent);
|
||
|
}
|
||
|
catch (e) {
|
||
|
_this._logError(e);
|
||
|
}
|
||
|
}, duration);
|
||
|
this._timeoutIds[timeoutId] = true;
|
||
|
}
|
||
|
return timeoutId;
|
||
|
};
|
||
|
/**
|
||
|
* Clears the timeout.
|
||
|
* @param id - Id to cancel.
|
||
|
*/
|
||
|
Async.prototype.clearTimeout = function (id) {
|
||
|
if (this._timeoutIds && this._timeoutIds[id]) {
|
||
|
clearTimeout(id);
|
||
|
delete this._timeoutIds[id];
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* SetImmediate override, which will auto cancel the immediate during dispose.
|
||
|
* @param callback - Callback to execute.
|
||
|
* @param targetElement - Optional target element to use for identifying the correct window.
|
||
|
* @returns The setTimeout id.
|
||
|
*/
|
||
|
Async.prototype.setImmediate = function (callback, targetElement) {
|
||
|
var _this = this;
|
||
|
var immediateId = 0;
|
||
|
var win = (0, getWindow_1.getWindow)(targetElement);
|
||
|
if (!this._isDisposed) {
|
||
|
if (!this._immediateIds) {
|
||
|
this._immediateIds = {};
|
||
|
}
|
||
|
var setImmediateCallback = function () {
|
||
|
// 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) {
|
||
|
_this._logError(e);
|
||
|
}
|
||
|
};
|
||
|
immediateId = win.setTimeout(setImmediateCallback, 0);
|
||
|
this._immediateIds[immediateId] = true;
|
||
|
}
|
||
|
return immediateId;
|
||
|
};
|
||
|
/**
|
||
|
* Clears the immediate.
|
||
|
* @param id - Id to cancel.
|
||
|
* @param targetElement - Optional target element to use for identifying the correct window.
|
||
|
*/
|
||
|
Async.prototype.clearImmediate = function (id, targetElement) {
|
||
|
var win = (0, getWindow_1.getWindow)(targetElement);
|
||
|
if (this._immediateIds && this._immediateIds[id]) {
|
||
|
win.clearTimeout(id);
|
||
|
delete this._immediateIds[id];
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* SetInterval override, which will auto cancel the timeout during dispose.
|
||
|
* @param callback - Callback to execute.
|
||
|
* @param duration - Duration in milliseconds.
|
||
|
* @returns The setTimeout id.
|
||
|
*/
|
||
|
Async.prototype.setInterval = function (callback, duration) {
|
||
|
var _this = this;
|
||
|
var intervalId = 0;
|
||
|
if (!this._isDisposed) {
|
||
|
if (!this._intervalIds) {
|
||
|
this._intervalIds = {};
|
||
|
}
|
||
|
intervalId = setInterval(function () {
|
||
|
// Time to execute the interval callback, enqueue it as a foreground task to be executed.
|
||
|
try {
|
||
|
callback.apply(_this._parent);
|
||
|
}
|
||
|
catch (e) {
|
||
|
_this._logError(e);
|
||
|
}
|
||
|
}, duration);
|
||
|
this._intervalIds[intervalId] = true;
|
||
|
}
|
||
|
return intervalId;
|
||
|
};
|
||
|
/**
|
||
|
* Clears the interval.
|
||
|
* @param id - Id to cancel.
|
||
|
*/
|
||
|
Async.prototype.clearInterval = function (id) {
|
||
|
if (this._intervalIds && this._intervalIds[id]) {
|
||
|
clearInterval(id);
|
||
|
delete this._intervalIds[id];
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* 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 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.
|
||
|
*/
|
||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
Async.prototype.throttle = function (func, wait, options) {
|
||
|
var _this = this;
|
||
|
if (this._isDisposed) {
|
||
|
return this._noop;
|
||
|
}
|
||
|
var waitMS = wait || 0;
|
||
|
var leading = true;
|
||
|
var trailing = true;
|
||
|
var lastExecuteTime = 0;
|
||
|
var lastResult;
|
||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
var lastArgs;
|
||
|
var timeoutId = null;
|
||
|
if (options && typeof options.leading === 'boolean') {
|
||
|
leading = options.leading;
|
||
|
}
|
||
|
if (options && typeof options.trailing === 'boolean') {
|
||
|
trailing = options.trailing;
|
||
|
}
|
||
|
var callback = function (userCall) {
|
||
|
var now = Date.now();
|
||
|
var delta = now - lastExecuteTime;
|
||
|
var waitLength = leading ? waitMS - delta : waitMS;
|
||
|
if (delta >= waitMS && (!userCall || leading)) {
|
||
|
lastExecuteTime = now;
|
||
|
if (timeoutId) {
|
||
|
_this.clearTimeout(timeoutId);
|
||
|
timeoutId = null;
|
||
|
}
|
||
|
lastResult = func.apply(_this._parent, lastArgs);
|
||
|
}
|
||
|
else if (timeoutId === null && trailing) {
|
||
|
timeoutId = _this.setTimeout(callback, waitLength);
|
||
|
}
|
||
|
return lastResult;
|
||
|
};
|
||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
var resultFunction = (function () {
|
||
|
var args = [];
|
||
|
for (var _i = 0; _i < arguments.length; _i++) {
|
||
|
args[_i] = arguments[_i];
|
||
|
}
|
||
|
lastArgs = args;
|
||
|
return callback(true);
|
||
|
});
|
||
|
return resultFunction;
|
||
|
};
|
||
|
/**
|
||
|
* 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 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.
|
||
|
*/
|
||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
Async.prototype.debounce = function (func, wait, options) {
|
||
|
var _this = this;
|
||
|
if (this._isDisposed) {
|
||
|
var noOpFunction = (function () {
|
||
|
/** Do nothing */
|
||
|
});
|
||
|
noOpFunction.cancel = function () {
|
||
|
return;
|
||
|
};
|
||
|
noOpFunction.flush = (function () { return null; });
|
||
|
noOpFunction.pending = function () { return false; };
|
||
|
return noOpFunction;
|
||
|
}
|
||
|
var waitMS = wait || 0;
|
||
|
var leading = false;
|
||
|
var trailing = true;
|
||
|
var maxWait = null;
|
||
|
var lastCallTime = 0;
|
||
|
var lastExecuteTime = Date.now();
|
||
|
var lastResult;
|
||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
var lastArgs;
|
||
|
var timeoutId = null;
|
||
|
if (options && typeof options.leading === 'boolean') {
|
||
|
leading = options.leading;
|
||
|
}
|
||
|
if (options && typeof options.trailing === 'boolean') {
|
||
|
trailing = options.trailing;
|
||
|
}
|
||
|
if (options && typeof options.maxWait === 'number' && !isNaN(options.maxWait)) {
|
||
|
maxWait = options.maxWait;
|
||
|
}
|
||
|
var markExecuted = function (time) {
|
||
|
if (timeoutId) {
|
||
|
_this.clearTimeout(timeoutId);
|
||
|
timeoutId = null;
|
||
|
}
|
||
|
lastExecuteTime = time;
|
||
|
};
|
||
|
var invokeFunction = function (time) {
|
||
|
markExecuted(time);
|
||
|
lastResult = func.apply(_this._parent, lastArgs);
|
||
|
};
|
||
|
var callback = function (userCall) {
|
||
|
var now = Date.now();
|
||
|
var executeImmediately = false;
|
||
|
if (userCall) {
|
||
|
if (leading && now - lastCallTime >= waitMS) {
|
||
|
executeImmediately = true;
|
||
|
}
|
||
|
lastCallTime = now;
|
||
|
}
|
||
|
var delta = now - lastCallTime;
|
||
|
var waitLength = waitMS - delta;
|
||
|
var maxWaitDelta = now - lastExecuteTime;
|
||
|
var 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;
|
||
|
};
|
||
|
var pending = function () {
|
||
|
return !!timeoutId;
|
||
|
};
|
||
|
var cancel = function () {
|
||
|
if (pending()) {
|
||
|
// Mark the debounced function as having executed
|
||
|
markExecuted(Date.now());
|
||
|
}
|
||
|
};
|
||
|
var flush = function () {
|
||
|
if (pending()) {
|
||
|
invokeFunction(Date.now());
|
||
|
}
|
||
|
return lastResult;
|
||
|
};
|
||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
var resultFunction = (function () {
|
||
|
var args = [];
|
||
|
for (var _i = 0; _i < arguments.length; _i++) {
|
||
|
args[_i] = arguments[_i];
|
||
|
}
|
||
|
lastArgs = args;
|
||
|
return callback(true);
|
||
|
});
|
||
|
resultFunction.cancel = cancel;
|
||
|
resultFunction.flush = flush;
|
||
|
resultFunction.pending = pending;
|
||
|
return resultFunction;
|
||
|
};
|
||
|
Async.prototype.requestAnimationFrame = function (callback, targetElement) {
|
||
|
var _this = this;
|
||
|
var animationFrameId = 0;
|
||
|
var win = (0, getWindow_1.getWindow)(targetElement);
|
||
|
if (!this._isDisposed) {
|
||
|
if (!this._animationFrameIds) {
|
||
|
this._animationFrameIds = {};
|
||
|
}
|
||
|
var animationFrameCallback = function () {
|
||
|
try {
|
||
|
// Now delete the record and call the callback.
|
||
|
if (_this._animationFrameIds) {
|
||
|
delete _this._animationFrameIds[animationFrameId];
|
||
|
}
|
||
|
callback.apply(_this._parent);
|
||
|
}
|
||
|
catch (e) {
|
||
|
_this._logError(e);
|
||
|
}
|
||
|
};
|
||
|
animationFrameId = win.requestAnimationFrame
|
||
|
? win.requestAnimationFrame(animationFrameCallback)
|
||
|
: win.setTimeout(animationFrameCallback, 0);
|
||
|
this._animationFrameIds[animationFrameId] = true;
|
||
|
}
|
||
|
return animationFrameId;
|
||
|
};
|
||
|
Async.prototype.cancelAnimationFrame = function (id, targetElement) {
|
||
|
var win = (0, getWindow_1.getWindow)(targetElement);
|
||
|
if (this._animationFrameIds && this._animationFrameIds[id]) {
|
||
|
win.cancelAnimationFrame ? win.cancelAnimationFrame(id) : win.clearTimeout(id);
|
||
|
delete this._animationFrameIds[id];
|
||
|
}
|
||
|
};
|
||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
Async.prototype._logError = function (e) {
|
||
|
if (this._onErrorHandler) {
|
||
|
this._onErrorHandler(e);
|
||
|
}
|
||
|
};
|
||
|
return Async;
|
||
|
}());
|
||
|
exports.Async = Async;
|
||
|
});
|
||
|
//# sourceMappingURL=Async.js.map
|