UNPKG

@uifabric/utilities

Version:

Fluent UI React utilities for building components.

414 lines 15.6 kB
import { getWindow } from './dom/getWindow'; /** * 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) { if (_this._onErrorHandler) { _this._onErrorHandler(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 = 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 = 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 = 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 = 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; }()); export { Async }; //# sourceMappingURL=Async.js.map