| /* | 
 | * Licensed to the Apache Software Foundation (ASF) under one | 
 | * or more contributor license agreements.  See the NOTICE file | 
 | * distributed with this work for additional information | 
 | * regarding copyright ownership.  The ASF licenses this file | 
 | * to you under the Apache License, Version 2.0 (the | 
 | * "License"); you may not use this file except in compliance | 
 | * with the License.  You may obtain a copy of the License at | 
 | * | 
 | *   http://www.apache.org/licenses/LICENSE-2.0 | 
 | * | 
 | * Unless required by applicable law or agreed to in writing, | 
 | * software distributed under the License is distributed on an | 
 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
 | * KIND, either express or implied.  See the License for the | 
 | * specific language governing permissions and limitations | 
 | * under the License. | 
 | */ | 
 |  | 
 |  | 
 | const ORIGIN_METHOD = '\0__throttleOriginMethod' as const; | 
 | const RATE = '\0__throttleRate' as const; | 
 | const THROTTLE_TYPE = '\0__throttleType' as const; | 
 |  | 
 | type ThrottleFunction = (this: unknown, ...args: unknown[]) => void; | 
 | export type ThrottleType = 'fixRate' | 'debounce'; | 
 |  | 
 | export interface ThrottleController { | 
 |     clear(): void; | 
 |     debounceNextCall(debounceDelay: number): void; | 
 | }; | 
 |  | 
 | /** | 
 |  * @public | 
 |  * @param {(Function)} fn | 
 |  * @param {number} [delay=0] Unit: ms. | 
 |  * @param {boolean} [debounce=false] | 
 |  *        true: If call interval less than `delay`, only the last call works. | 
 |  *        false: If call interval less than `delay, call works on fixed rate. | 
 |  * @return {(Function)} throttled fn. | 
 |  */ | 
 | export function throttle<T extends ThrottleFunction>( | 
 |     fn: T, | 
 |     delay?: number, | 
 |     debounce?: boolean | 
 | ): T & ThrottleController { | 
 |  | 
 |     let currCall; | 
 |     let lastCall = 0; | 
 |     let lastExec = 0; | 
 |     let timer: ReturnType<typeof setTimeout> = null; | 
 |     let diff; | 
 |     let scope: unknown; | 
 |     let args: unknown[]; | 
 |     let debounceNextCall: number; | 
 |  | 
 |     delay = delay || 0; | 
 |  | 
 |     function exec(): void { | 
 |         lastExec = (new Date()).getTime(); | 
 |         timer = null; | 
 |         fn.apply(scope, args || []); | 
 |     } | 
 |  | 
 |     const cb = function (this: unknown, ...cbArgs: unknown[]): void { | 
 |         currCall = (new Date()).getTime(); | 
 |         scope = this; | 
 |         args = cbArgs; | 
 |         const thisDelay = debounceNextCall || delay; | 
 |         const thisDebounce = debounceNextCall || debounce; | 
 |         debounceNextCall = null; | 
 |         diff = currCall - (thisDebounce ? lastCall : lastExec) - thisDelay; | 
 |  | 
 |         clearTimeout(timer); | 
 |  | 
 |         // Here we should make sure that: the `exec` SHOULD NOT be called later | 
 |         // than a new call of `cb`, that is, preserving the command order. Consider | 
 |         // calculating "scale rate" when roaming as an example. When a call of `cb` | 
 |         // happens, either the `exec` is called dierectly, or the call is delayed. | 
 |         // But the delayed call should never be later than next call of `cb`. Under | 
 |         // this assurance, we can simply update view state each time `dispatchAction` | 
 |         // triggered by user roaming, but not need to add extra code to avoid the | 
 |         // state being "rolled-back". | 
 |         if (thisDebounce) { | 
 |             timer = setTimeout(exec, thisDelay); | 
 |         } | 
 |         else { | 
 |             if (diff >= 0) { | 
 |                 exec(); | 
 |             } | 
 |             else { | 
 |                 timer = setTimeout(exec, -diff); | 
 |             } | 
 |         } | 
 |  | 
 |         lastCall = currCall; | 
 |     } as T & ThrottleController; | 
 |  | 
 |     /** | 
 |      * Clear throttle. | 
 |      * @public | 
 |      */ | 
 |     cb.clear = function (): void { | 
 |         if (timer) { | 
 |             clearTimeout(timer); | 
 |             timer = null; | 
 |         } | 
 |     }; | 
 |  | 
 |     /** | 
 |      * Enable debounce once. | 
 |      */ | 
 |     cb.debounceNextCall = function (debounceDelay: number): void { | 
 |         debounceNextCall = debounceDelay; | 
 |     }; | 
 |  | 
 |     return cb; | 
 | } | 
 |  | 
 | /** | 
 |  * Create throttle method or update throttle rate. | 
 |  * | 
 |  * @example | 
 |  * ComponentView.prototype.render = function () { | 
 |  *     ... | 
 |  *     throttle.createOrUpdate( | 
 |  *         this, | 
 |  *         '_dispatchAction', | 
 |  *         this.model.get('throttle'), | 
 |  *         'fixRate' | 
 |  *     ); | 
 |  * }; | 
 |  * ComponentView.prototype.remove = function () { | 
 |  *     throttle.clear(this, '_dispatchAction'); | 
 |  * }; | 
 |  * ComponentView.prototype.dispose = function () { | 
 |  *     throttle.clear(this, '_dispatchAction'); | 
 |  * }; | 
 |  * | 
 |  */ | 
 | export function createOrUpdate<T, S extends keyof T, P = T[S]>( | 
 |     obj: T, | 
 |     fnAttr: S, | 
 |     rate: number, | 
 |     throttleType: ThrottleType | 
 | ): P extends ThrottleFunction ? P & ThrottleController : never { | 
 |     let fn = obj[fnAttr]; | 
 |  | 
 |     if (!fn) { | 
 |         return; | 
 |     } | 
 |  | 
 |     const originFn = (fn as any)[ORIGIN_METHOD] || fn; | 
 |     const lastThrottleType = (fn as any)[THROTTLE_TYPE]; | 
 |     const lastRate = (fn as any)[RATE]; | 
 |  | 
 |     if (lastRate !== rate || lastThrottleType !== throttleType) { | 
 |         if (rate == null || !throttleType) { | 
 |             return (obj[fnAttr] = originFn); | 
 |         } | 
 |  | 
 |         fn = obj[fnAttr] = throttle( | 
 |             originFn, rate, throttleType === 'debounce' | 
 |         ); | 
 |         (fn as any)[ORIGIN_METHOD] = originFn; | 
 |         (fn as any)[THROTTLE_TYPE] = throttleType; | 
 |         (fn as any)[RATE] = rate; | 
 |     } | 
 |  | 
 |     return fn as ReturnType<typeof createOrUpdate>; | 
 | } | 
 |  | 
 | /** | 
 |  * Clear throttle. Example see throttle.createOrUpdate. | 
 |  */ | 
 | export function clear<T, S extends keyof T>(obj: T, fnAttr: S): void { | 
 |     const fn = obj[fnAttr]; | 
 |     if (fn && (fn as any)[ORIGIN_METHOD]) { | 
 |         // Clear throttle | 
 |         (fn as any).clear && (fn as any).clear(); | 
 |         obj[fnAttr] = (fn as any)[ORIGIN_METHOD]; | 
 |     } | 
 | } |