UNPKG

17.5 kBJavaScriptView Raw
1define(["require", "exports", "./dom/getWindow"], function (require, exports, getWindow_1) {
2 "use strict";
3 Object.defineProperty(exports, "__esModule", { value: true });
4 /**
5 * Bugs often appear in async code when stuff gets disposed, but async operations don't get canceled.
6 * This Async helper class solves these issues by tying async code to the lifetime of a disposable object.
7 *
8 * Usage: Anything class extending from BaseModel can access this helper via this.async. Otherwise create a
9 * new instance of the class and remember to call dispose() during your code's dispose handler.
10 *
11 * @public
12 */
13 var Async = /** @class */ (function () {
14 // eslint-disable-next-line @typescript-eslint/no-explicit-any
15 function Async(parent, onError) {
16 this._timeoutIds = null;
17 this._immediateIds = null;
18 this._intervalIds = null;
19 this._animationFrameIds = null;
20 this._isDisposed = false;
21 this._parent = parent || null;
22 this._onErrorHandler = onError;
23 this._noop = function () {
24 /* do nothing */
25 };
26 }
27 /**
28 * Dispose function, clears all async operations.
29 */
30 Async.prototype.dispose = function () {
31 var id;
32 this._isDisposed = true;
33 this._parent = null;
34 // Clear timeouts.
35 if (this._timeoutIds) {
36 for (id in this._timeoutIds) {
37 if (this._timeoutIds.hasOwnProperty(id)) {
38 this.clearTimeout(parseInt(id, 10));
39 }
40 }
41 this._timeoutIds = null;
42 }
43 // Clear immediates.
44 if (this._immediateIds) {
45 for (id in this._immediateIds) {
46 if (this._immediateIds.hasOwnProperty(id)) {
47 this.clearImmediate(parseInt(id, 10));
48 }
49 }
50 this._immediateIds = null;
51 }
52 // Clear intervals.
53 if (this._intervalIds) {
54 for (id in this._intervalIds) {
55 if (this._intervalIds.hasOwnProperty(id)) {
56 this.clearInterval(parseInt(id, 10));
57 }
58 }
59 this._intervalIds = null;
60 }
61 // Clear animation frames.
62 if (this._animationFrameIds) {
63 for (id in this._animationFrameIds) {
64 if (this._animationFrameIds.hasOwnProperty(id)) {
65 this.cancelAnimationFrame(parseInt(id, 10));
66 }
67 }
68 this._animationFrameIds = null;
69 }
70 };
71 /**
72 * SetTimeout override, which will auto cancel the timeout during dispose.
73 * @param callback - Callback to execute.
74 * @param duration - Duration in milliseconds.
75 * @returns The setTimeout id.
76 */
77 Async.prototype.setTimeout = function (callback, duration) {
78 var _this = this;
79 var timeoutId = 0;
80 if (!this._isDisposed) {
81 if (!this._timeoutIds) {
82 this._timeoutIds = {};
83 }
84 timeoutId = setTimeout(function () {
85 // Time to execute the timeout, enqueue it as a foreground task to be executed.
86 try {
87 // Now delete the record and call the callback.
88 if (_this._timeoutIds) {
89 delete _this._timeoutIds[timeoutId];
90 }
91 callback.apply(_this._parent);
92 }
93 catch (e) {
94 if (_this._onErrorHandler) {
95 _this._onErrorHandler(e);
96 }
97 }
98 }, duration);
99 this._timeoutIds[timeoutId] = true;
100 }
101 return timeoutId;
102 };
103 /**
104 * Clears the timeout.
105 * @param id - Id to cancel.
106 */
107 Async.prototype.clearTimeout = function (id) {
108 if (this._timeoutIds && this._timeoutIds[id]) {
109 clearTimeout(id);
110 delete this._timeoutIds[id];
111 }
112 };
113 /**
114 * SetImmediate override, which will auto cancel the immediate during dispose.
115 * @param callback - Callback to execute.
116 * @param targetElement - Optional target element to use for identifying the correct window.
117 * @returns The setTimeout id.
118 */
119 Async.prototype.setImmediate = function (callback, targetElement) {
120 var _this = this;
121 var immediateId = 0;
122 var win = getWindow_1.getWindow(targetElement);
123 if (!this._isDisposed) {
124 if (!this._immediateIds) {
125 this._immediateIds = {};
126 }
127 var setImmediateCallback = function () {
128 // Time to execute the timeout, enqueue it as a foreground task to be executed.
129 try {
130 // Now delete the record and call the callback.
131 if (_this._immediateIds) {
132 delete _this._immediateIds[immediateId];
133 }
134 callback.apply(_this._parent);
135 }
136 catch (e) {
137 _this._logError(e);
138 }
139 };
140 immediateId = win.setTimeout(setImmediateCallback, 0);
141 this._immediateIds[immediateId] = true;
142 }
143 return immediateId;
144 };
145 /**
146 * Clears the immediate.
147 * @param id - Id to cancel.
148 * @param targetElement - Optional target element to use for identifying the correct window.
149 */
150 Async.prototype.clearImmediate = function (id, targetElement) {
151 var win = getWindow_1.getWindow(targetElement);
152 if (this._immediateIds && this._immediateIds[id]) {
153 win.clearTimeout(id);
154 delete this._immediateIds[id];
155 }
156 };
157 /**
158 * SetInterval override, which will auto cancel the timeout during dispose.
159 * @param callback - Callback to execute.
160 * @param duration - Duration in milliseconds.
161 * @returns The setTimeout id.
162 */
163 Async.prototype.setInterval = function (callback, duration) {
164 var _this = this;
165 var intervalId = 0;
166 if (!this._isDisposed) {
167 if (!this._intervalIds) {
168 this._intervalIds = {};
169 }
170 intervalId = setInterval(function () {
171 // Time to execute the interval callback, enqueue it as a foreground task to be executed.
172 try {
173 callback.apply(_this._parent);
174 }
175 catch (e) {
176 _this._logError(e);
177 }
178 }, duration);
179 this._intervalIds[intervalId] = true;
180 }
181 return intervalId;
182 };
183 /**
184 * Clears the interval.
185 * @param id - Id to cancel.
186 */
187 Async.prototype.clearInterval = function (id) {
188 if (this._intervalIds && this._intervalIds[id]) {
189 clearInterval(id);
190 delete this._intervalIds[id];
191 }
192 };
193 /**
194 * Creates a function that, when executed, will only call the func function at most once per
195 * every wait milliseconds. Provide an options object to indicate that func should be invoked
196 * on the leading and/or trailing edge of the wait timeout. Subsequent calls to the throttled
197 * function will return the result of the last func call.
198 *
199 * Note: If leading and trailing options are true func will be called on the trailing edge of
200 * the timeout only if the throttled function is invoked more than once during the wait timeout.
201 *
202 * @param func - The function to throttle.
203 * @param wait - The number of milliseconds to throttle executions to. Defaults to 0.
204 * @param options - The options object.
205 * @returns The new throttled function.
206 */
207 // eslint-disable-next-line @typescript-eslint/no-explicit-any
208 Async.prototype.throttle = function (func, wait, options) {
209 var _this = this;
210 if (this._isDisposed) {
211 return this._noop;
212 }
213 var waitMS = wait || 0;
214 var leading = true;
215 var trailing = true;
216 var lastExecuteTime = 0;
217 var lastResult;
218 // eslint-disable-next-line @typescript-eslint/no-explicit-any
219 var lastArgs;
220 var timeoutId = null;
221 if (options && typeof options.leading === 'boolean') {
222 leading = options.leading;
223 }
224 if (options && typeof options.trailing === 'boolean') {
225 trailing = options.trailing;
226 }
227 var callback = function (userCall) {
228 var now = Date.now();
229 var delta = now - lastExecuteTime;
230 var waitLength = leading ? waitMS - delta : waitMS;
231 if (delta >= waitMS && (!userCall || leading)) {
232 lastExecuteTime = now;
233 if (timeoutId) {
234 _this.clearTimeout(timeoutId);
235 timeoutId = null;
236 }
237 lastResult = func.apply(_this._parent, lastArgs);
238 }
239 else if (timeoutId === null && trailing) {
240 timeoutId = _this.setTimeout(callback, waitLength);
241 }
242 return lastResult;
243 };
244 // eslint-disable-next-line @typescript-eslint/no-explicit-any
245 var resultFunction = (function () {
246 var args = [];
247 for (var _i = 0; _i < arguments.length; _i++) {
248 args[_i] = arguments[_i];
249 }
250 lastArgs = args;
251 return callback(true);
252 });
253 return resultFunction;
254 };
255 /**
256 * Creates a function that will delay the execution of func until after wait milliseconds have
257 * elapsed since the last time it was invoked. Provide an options object to indicate that func
258 * should be invoked on the leading and/or trailing edge of the wait timeout. Subsequent calls
259 * to the debounced function will return the result of the last func call.
260 *
261 * Note: If leading and trailing options are true func will be called on the trailing edge of
262 * the timeout only if the debounced function is invoked more than once during the wait
263 * timeout.
264 *
265 * @param func - The function to debounce.
266 * @param wait - The number of milliseconds to delay.
267 * @param options - The options object.
268 * @returns The new debounced function.
269 */
270 // eslint-disable-next-line @typescript-eslint/no-explicit-any
271 Async.prototype.debounce = function (func, wait, options) {
272 var _this = this;
273 if (this._isDisposed) {
274 var noOpFunction = (function () {
275 /** Do nothing */
276 });
277 noOpFunction.cancel = function () {
278 return;
279 };
280 noOpFunction.flush = (function () { return null; });
281 noOpFunction.pending = function () { return false; };
282 return noOpFunction;
283 }
284 var waitMS = wait || 0;
285 var leading = false;
286 var trailing = true;
287 var maxWait = null;
288 var lastCallTime = 0;
289 var lastExecuteTime = Date.now();
290 var lastResult;
291 // eslint-disable-next-line @typescript-eslint/no-explicit-any
292 var lastArgs;
293 var timeoutId = null;
294 if (options && typeof options.leading === 'boolean') {
295 leading = options.leading;
296 }
297 if (options && typeof options.trailing === 'boolean') {
298 trailing = options.trailing;
299 }
300 if (options && typeof options.maxWait === 'number' && !isNaN(options.maxWait)) {
301 maxWait = options.maxWait;
302 }
303 var markExecuted = function (time) {
304 if (timeoutId) {
305 _this.clearTimeout(timeoutId);
306 timeoutId = null;
307 }
308 lastExecuteTime = time;
309 };
310 var invokeFunction = function (time) {
311 markExecuted(time);
312 lastResult = func.apply(_this._parent, lastArgs);
313 };
314 var callback = function (userCall) {
315 var now = Date.now();
316 var executeImmediately = false;
317 if (userCall) {
318 if (leading && now - lastCallTime >= waitMS) {
319 executeImmediately = true;
320 }
321 lastCallTime = now;
322 }
323 var delta = now - lastCallTime;
324 var waitLength = waitMS - delta;
325 var maxWaitDelta = now - lastExecuteTime;
326 var maxWaitExpired = false;
327 if (maxWait !== null) {
328 // maxWait only matters when there is a pending callback
329 if (maxWaitDelta >= maxWait && timeoutId) {
330 maxWaitExpired = true;
331 }
332 else {
333 waitLength = Math.min(waitLength, maxWait - maxWaitDelta);
334 }
335 }
336 if (delta >= waitMS || maxWaitExpired || executeImmediately) {
337 invokeFunction(now);
338 }
339 else if ((timeoutId === null || !userCall) && trailing) {
340 timeoutId = _this.setTimeout(callback, waitLength);
341 }
342 return lastResult;
343 };
344 var pending = function () {
345 return !!timeoutId;
346 };
347 var cancel = function () {
348 if (pending()) {
349 // Mark the debounced function as having executed
350 markExecuted(Date.now());
351 }
352 };
353 var flush = function () {
354 if (pending()) {
355 invokeFunction(Date.now());
356 }
357 return lastResult;
358 };
359 // eslint-disable-next-line @typescript-eslint/no-explicit-any
360 var resultFunction = (function () {
361 var args = [];
362 for (var _i = 0; _i < arguments.length; _i++) {
363 args[_i] = arguments[_i];
364 }
365 lastArgs = args;
366 return callback(true);
367 });
368 resultFunction.cancel = cancel;
369 resultFunction.flush = flush;
370 resultFunction.pending = pending;
371 return resultFunction;
372 };
373 Async.prototype.requestAnimationFrame = function (callback, targetElement) {
374 var _this = this;
375 var animationFrameId = 0;
376 var win = getWindow_1.getWindow(targetElement);
377 if (!this._isDisposed) {
378 if (!this._animationFrameIds) {
379 this._animationFrameIds = {};
380 }
381 var animationFrameCallback = function () {
382 try {
383 // Now delete the record and call the callback.
384 if (_this._animationFrameIds) {
385 delete _this._animationFrameIds[animationFrameId];
386 }
387 callback.apply(_this._parent);
388 }
389 catch (e) {
390 _this._logError(e);
391 }
392 };
393 animationFrameId = win.requestAnimationFrame
394 ? win.requestAnimationFrame(animationFrameCallback)
395 : win.setTimeout(animationFrameCallback, 0);
396 this._animationFrameIds[animationFrameId] = true;
397 }
398 return animationFrameId;
399 };
400 Async.prototype.cancelAnimationFrame = function (id, targetElement) {
401 var win = getWindow_1.getWindow(targetElement);
402 if (this._animationFrameIds && this._animationFrameIds[id]) {
403 win.cancelAnimationFrame ? win.cancelAnimationFrame(id) : win.clearTimeout(id);
404 delete this._animationFrameIds[id];
405 }
406 };
407 // eslint-disable-next-line @typescript-eslint/no-explicit-any
408 Async.prototype._logError = function (e) {
409 if (this._onErrorHandler) {
410 this._onErrorHandler(e);
411 }
412 };
413 return Async;
414 }());
415 exports.Async = Async;
416});
417//# sourceMappingURL=Async.js.map
\No newline at end of file