1 | define(["require", "exports", "./dom/getWindow"], function (require, exports, getWindow_1) {
|
2 | ;
|
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 |