|
@@ -0,0 +1,166 @@
|
|
|
|
|
+// fork from https://github.com/toss/es-toolkit/blob/main/src/function/debounce.ts
|
|
|
|
|
+// 文档可查看:https://es-toolkit.dev/reference/function/debounce.html
|
|
|
|
|
+// 如需要 throttle 功能,可 copy https://github.com/toss/es-toolkit/blob/main/src/function/throttle.ts
|
|
|
|
|
+
|
|
|
|
|
+interface DebounceOptions {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * An optional AbortSignal to cancel the debounced function.
|
|
|
|
|
+ */
|
|
|
|
|
+ signal?: AbortSignal
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both.
|
|
|
|
|
+ * If `edges` includes "leading", the function will be invoked at the start of the delay period.
|
|
|
|
|
+ * If `edges` includes "trailing", the function will be invoked at the end of the delay period.
|
|
|
|
|
+ * If both "leading" and "trailing" are included, the function will be invoked at both the start and end of the delay period.
|
|
|
|
|
+ * @default ["trailing"]
|
|
|
|
|
+ */
|
|
|
|
|
+ edges?: Array<'leading' | 'trailing'>
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface DebouncedFunction<F extends (...args: any[]) => void> {
|
|
|
|
|
+ (...args: Parameters<F>): void
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Schedules the execution of the debounced function after the specified debounce delay.
|
|
|
|
|
+ * This method resets any existing timer, ensuring that the function is only invoked
|
|
|
|
|
+ * after the delay has elapsed since the last call to the debounced function.
|
|
|
|
|
+ * It is typically called internally whenever the debounced function is invoked.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @returns {void}
|
|
|
|
|
+ */
|
|
|
|
|
+ schedule: () => void
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Cancels any pending execution of the debounced function.
|
|
|
|
|
+ * This method clears the active timer and resets any stored context or arguments.
|
|
|
|
|
+ */
|
|
|
|
|
+ cancel: () => void
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Immediately invokes the debounced function if there is a pending execution.
|
|
|
|
|
+ * This method executes the function right away if there is a pending execution.
|
|
|
|
|
+ */
|
|
|
|
|
+ flush: () => void
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Creates a debounced function that delays invoking the provided function until after `debounceMs` milliseconds
|
|
|
|
|
+ * have elapsed since the last time the debounced function was invoked. The debounced function also has a `cancel`
|
|
|
|
|
+ * method to cancel any pending execution.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @template F - The type of function.
|
|
|
|
|
+ * @param {F} func - The function to debounce.
|
|
|
|
|
+ * @param {number} debounceMs - The number of milliseconds to delay.
|
|
|
|
|
+ * @param {DebounceOptions} options - The options object
|
|
|
|
|
+ * @param {AbortSignal} options.signal - An optional AbortSignal to cancel the debounced function.
|
|
|
|
|
+ * @returns A new debounced function with a `cancel` method.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @example
|
|
|
|
|
+ * const debouncedFunction = debounce(() => {
|
|
|
|
|
+ * console.log('Function executed');
|
|
|
|
|
+ * }, 1000);
|
|
|
|
|
+ *
|
|
|
|
|
+ * // Will log 'Function executed' after 1 second if not called again in that time
|
|
|
|
|
+ * debouncedFunction();
|
|
|
|
|
+ *
|
|
|
|
|
+ * // Will not log anything as the previous call is canceled
|
|
|
|
|
+ * debouncedFunction.cancel();
|
|
|
|
|
+ *
|
|
|
|
|
+ * // With AbortSignal
|
|
|
|
|
+ * const controller = new AbortController();
|
|
|
|
|
+ * const signal = controller.signal;
|
|
|
|
|
+ * const debouncedWithSignal = debounce(() => {
|
|
|
|
|
+ * console.log('Function executed');
|
|
|
|
|
+ * }, 1000, { signal });
|
|
|
|
|
+ *
|
|
|
|
|
+ * debouncedWithSignal();
|
|
|
|
|
+ *
|
|
|
|
|
+ * // Will cancel the debounced function call
|
|
|
|
|
+ * controller.abort();
|
|
|
|
|
+ */
|
|
|
|
|
+export function debounce<F extends (...args: any[]) => void>(
|
|
|
|
|
+ func: F,
|
|
|
|
|
+ debounceMs: number,
|
|
|
|
|
+ { signal, edges }: DebounceOptions = {},
|
|
|
|
|
+): DebouncedFunction<F> {
|
|
|
|
|
+ let pendingThis: any
|
|
|
|
|
+ let pendingArgs: Parameters<F> | null = null
|
|
|
|
|
+
|
|
|
|
|
+ const leading = edges != null && edges.includes('leading')
|
|
|
|
|
+ const trailing = edges == null || edges.includes('trailing')
|
|
|
|
|
+
|
|
|
|
|
+ const invoke = () => {
|
|
|
|
|
+ if (pendingArgs !== null) {
|
|
|
|
|
+ func.apply(pendingThis, pendingArgs)
|
|
|
|
|
+ pendingThis = undefined
|
|
|
|
|
+ pendingArgs = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const onTimerEnd = () => {
|
|
|
|
|
+ if (trailing) {
|
|
|
|
|
+ invoke()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // eslint-disable-next-line ts/no-use-before-define
|
|
|
|
|
+ cancel()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
|
|
|
+
|
|
|
|
|
+ const schedule = () => {
|
|
|
|
|
+ if (timeoutId != null) {
|
|
|
|
|
+ clearTimeout(timeoutId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ timeoutId = setTimeout(() => {
|
|
|
|
|
+ timeoutId = null
|
|
|
|
|
+
|
|
|
|
|
+ onTimerEnd()
|
|
|
|
|
+ }, debounceMs)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const cancelTimer = () => {
|
|
|
|
|
+ if (timeoutId !== null) {
|
|
|
|
|
+ clearTimeout(timeoutId)
|
|
|
|
|
+ timeoutId = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const cancel = () => {
|
|
|
|
|
+ cancelTimer()
|
|
|
|
|
+ pendingThis = undefined
|
|
|
|
|
+ pendingArgs = null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const flush = () => {
|
|
|
|
|
+ invoke()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const debounced = function (this: any, ...args: Parameters<F>) {
|
|
|
|
|
+ if (signal?.aborted) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // eslint-disable-next-line ts/no-this-alias
|
|
|
|
|
+ pendingThis = this
|
|
|
|
|
+ pendingArgs = args
|
|
|
|
|
+
|
|
|
|
|
+ const isFirstCall = timeoutId == null
|
|
|
|
|
+
|
|
|
|
|
+ schedule()
|
|
|
|
|
+
|
|
|
|
|
+ if (leading && isFirstCall) {
|
|
|
|
|
+ invoke()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ debounced.schedule = schedule
|
|
|
|
|
+ debounced.cancel = cancel
|
|
|
|
|
+ debounced.flush = flush
|
|
|
|
|
+
|
|
|
|
|
+ signal?.addEventListener('abort', cancel, { once: true })
|
|
|
|
|
+
|
|
|
|
|
+ return debounced
|
|
|
|
|
+}
|