addons/shared/debounce.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318export type DebouncedFunction<Args extends Array<unknown>> = {
b69ab319 (...args: Args): void;
b69ab3110 reset: () => void;
b69ab3111 isPending: () => boolean;
b69ab3112 dispose: () => void;
b69ab3113};
b69ab3114
b69ab3115/**
b69ab3116 * This is a rate limiting mechanism, used to invoke a function after a repeated
b69ab3117 * action has completed. This creates and returns a debounced version of
b69ab3118 * the function passed in that will postpone its execution until after `wait`
b69ab3119 * milliseconds have elapsed since the last time it was invoked.
b69ab3120 *
b69ab3121 * For example, if you wanted to update a preview after the user stops typing
b69ab3122 * you could do the following:
b69ab3123 *
b69ab3124 * elem.addEventListener('keyup', debounce(this.updatePreview, 250), false);
b69ab3125 *
b69ab3126 * The returned function has a reset method which can be called to cancel a
b69ab3127 * pending invocation.
b69ab3128 *
b69ab3129 * var debouncedUpdatePreview = debounce(this.updatePreview, 250);
b69ab3130 * elem.addEventListener('keyup', debouncedUpdatePreview, false);
b69ab3131 *
b69ab3132 * // later, to cancel pending calls
b69ab3133 * debouncedUpdatePreview.reset();
b69ab3134 *
b69ab3135 * @param func - the function to debounce
b69ab3136 * @param wait - how long to wait in milliseconds
b69ab3137 * @param context - optional context to invoke the function in
b69ab3138 * @param leading - cause debounce to trigger the function on
b69ab3139 * the leading edge instead of the trailing edge of the wait interval
b69ab3140 */
b69ab3141export function debounce<Args extends Array<unknown>>(
b69ab3142 func: (...args: Args) => unknown,
b69ab3143 wait: number,
b69ab3144 context: unknown = undefined,
b69ab3145 leading = false,
b69ab3146): DebouncedFunction<Args> {
b69ab3147 let timeout: NodeJS.Timeout | undefined;
b69ab3148 let shouldCallLeading = true;
b69ab3149
b69ab3150 function debouncer(...args: Args) {
b69ab3151 let callback: () => void;
b69ab3152
b69ab3153 if (leading) {
b69ab3154 callback = function () {
b69ab3155 shouldCallLeading = true;
b69ab3156 clearTimeout(timeout);
b69ab3157 timeout = undefined;
b69ab3158 };
b69ab3159
b69ab3160 if (!shouldCallLeading) {
b69ab3161 clearTimeout(timeout);
b69ab3162 timeout = setTimeout(callback, wait);
b69ab3163 return;
b69ab3164 }
b69ab3165
b69ab3166 shouldCallLeading = false;
b69ab3167 func.apply(context, args);
b69ab3168 } else {
b69ab3169 debouncer.reset();
b69ab3170 callback = function () {
b69ab3171 clearTimeout(timeout);
b69ab3172 timeout = undefined;
b69ab3173 func.apply(context, args);
b69ab3174 };
b69ab3175 }
b69ab3176
b69ab3177 clearTimeout(timeout);
b69ab3178 timeout = setTimeout(callback, wait);
b69ab3179 }
b69ab3180
b69ab3181 debouncer.reset = function () {
b69ab3182 clearTimeout(timeout);
b69ab3183 timeout = undefined;
b69ab3184 shouldCallLeading = true;
b69ab3185 };
b69ab3186
b69ab3187 debouncer.dispose = function () {
b69ab3188 clearTimeout(timeout);
b69ab3189 timeout = undefined;
b69ab3190 };
b69ab3191
b69ab3192 debouncer.isPending = function () {
b69ab3193 return timeout != null;
b69ab3194 };
b69ab3195
b69ab3196 return debouncer;
b69ab3197}