HarmonyOS ClickUtil: Throttle & Debounce Patterns for Duplicate Click Prevention
This article analyzes the HarmonyOS ClickUtil library from @pura/harmony-utils V1.4.0, explaining throttle and debounce implementations with source code, interactive demos, and scenario-based selection guidance for preventing duplicate button clicks in mobile applications.
Overview
The article introduces @pura/harmony-utils version 1.4.0 by author "桃花镇童长老", focusing on the ClickUtil class that provides throttle and debounce static methods to prevent duplicate user operations in HarmonyOS applications.
Why Throttle and Debounce Are Needed
In mobile apps, users often tap buttons rapidly within short intervals. Without protection, this causes:
Duplicate form submissions : Users tap "Submit" multiple times due to slow network, sending identical requests to the server.
Duplicate navigation triggers : Rapid taps on navigation buttons push multiple identical pages onto the router stack.
API flooding : Search inputs fire a request on every keystroke, potentially generating dozens of requests per second.
Throttle and debounce are classic solutions for these problems.
Throttle vs Debounce: Core Differences
The author uses vivid analogies:
Throttle is like a subway gate: only one person passes per fixed interval; subsequent people must wait. Suits "rate limiting" scenarios.
Debounce is like an elevator door: it stays open while people enter, closing only after no one enters for a waiting period. Suits "wait for stop" scenarios.
A comparison table summarizes:
Core logic : Throttle executes once per fixed window; debounce executes after the last trigger plus a delay.
Trigger timing : Throttle can run immediately or at window end; debounce runs after activity stops.
Typical use cases : Throttle for duplicate submissions, scroll events; debounce for search input, window resize.
ClickUtil Source Code Analysis
1. Core State
export class ClickUtil { private static throttleTimeoutID: number; // throttle timeout ID private static flag: boolean = false; // throttle flag, true = in cooldown private static defaultId: string = DateUtil.getTodayTime().toString(); // debounce default ID} throttleTimeoutID: Stores the throttle timer ID for resetting after the wait period. flag: Gate flag for throttle; true means the cooldown period is active. defaultId: Default debounce identifier initialized with a timestamp, used when only one debounce event exists.
2. throttle — Throttle Implementation
static throttle(func: () => void, wait: number = 1000, immediate: boolean = true) { if (immediate) { if (!ClickUtil.flag) { ClickUtil.flag = true; typeof func === 'function' && func(); ClickUtil.throttleTimeoutID = setTimeout(() => { ClickUtil.flag = false; clearTimeout(ClickUtil.throttleTimeoutID); }, wait); } } else { if (!ClickUtil.flag) { ClickUtil.flag = true; ClickUtil.throttleTimeoutID = setTimeout(() => { ClickUtil.flag = false; typeof func === 'function' && func(); clearTimeout(ClickUtil.throttleTimeoutID); }, wait); } }}Parameters: func ( () => void, required): Callback to execute. wait ( number, default 1000): Cooldown interval in milliseconds. immediate ( boolean, default true): true = execute immediately then cool down; false = execute after cooldown ends.
Two modes timeline: immediate = true (immediate mode): Click → execute immediately → cooldown 1000ms → clickable again. immediate = false (delayed mode): Click → cooldown 1000ms → execute → clickable again.
3. debounce — Debounce Implementation
static debounce(func: () => void, wait: number = 1000, clickId: string = ClickUtil.defaultId) { let cacheID = CacheUtil.get<number>(`ClickUtil_debounce_timeoutID_${clickId}`); if (cacheID !== undefined && cacheID !== null) { clearTimeout(cacheID); } let timeoutID = setTimeout(() => { typeof func === 'function' && func(); clearTimeout(timeoutID); }, wait); CacheUtil.put<number>(`ClickUtil_debounce_timeoutID_${clickId}`, timeoutID);}Parameters: func ( () => void, required): Callback to execute. wait ( number, default 1000): Wait time in milliseconds after the last trigger. clickId ( string, default defaultId): Event identifier to distinguish multiple independent debounce events.
Working principle:
On each trigger, retrieve the previous timeoutID from CacheUtil using the key ClickUtil_debounce_timeoutID_${clickId}.
If a timer exists, call clearTimeout to cancel it.
Set a new timer with setTimeout.
Store the new timeoutID back into CacheUtil.
As long as triggers occur within the wait window, the timer is constantly reset; only after triggers stop and wait milliseconds elapse does the function execute.
Multiple event IDs design: The clickId parameter enables independent debounce timers. For example:
// Search input debounceClickUtil.debounce(() => this.doSearch(), 500, 'search_input');// Comment submit debounceClickUtil.debounce(() => this.submitComment(), 1000, 'comment_submit');These create separate cache keys: ClickUtil_debounce_timeoutID_search_input and ClickUtil_debounce_timeoutID_comment_submit, ensuring they do not interfere with each other.
Complete Demo Demonstration
The demo page CacheCharClickDemoPage.ets showcases both utilities.
1. Throttle Demo
@State throttleCount: number = 0;@State throttleImmediate: boolean = true;doThrottle() { ClickUtil.throttle(() => { this.throttleCount++; this.addLog('Throttle', `Triggered! Total: ${this.throttleCount} times`, 'success'); }, 1500, this.throttleImmediate);}2. Debounce Demo
@State debounceCount: number = 0;@State debounceDelay: number = 1000;doDebounce() { ClickUtil.debounce(() => { this.debounceCount++; this.addLog('Debounce', `Executed! Total: ${this.debounceCount} times`, 'success'); }, this.debounceDelay, 'demo_debounce');}3. Throttle UI
Includes a radio group to switch between "Immediate" ( immediate=true) and "Delayed" ( immediate=false) modes, and a button "快速连续点击我(节流)" bound to doThrottle().
4. Debounce UI
Includes a slider (300–3000ms, step 100) to adjust debounceDelay with live value display, and a button "快速连续点击我(防抖)" bound to doDebounce().
Running Effect Comparison
Throttle Test ( immediate=true , interval 1500ms)
Rapidly clicking 10 times within 1.5 seconds produces only one log entry: [Throttle] Triggered! Total: 1 times After waiting 1.5 seconds, another click adds a new log entry.
Debounce Test (delay 1000ms)
Rapidly clicking 10 times produces no immediate logs. After stopping and waiting 1 second, a single log appears:
[Debounce] Executed! Total: 1 timesHow to Choose: Throttle or Debounce?
A scenario-based selection guide:
Submit button anti-duplicate → Throttle ( immediate=true): First click responds immediately, subsequent clicks enter cooldown.
Search box real-time search → Debounce: Wait for user to stop typing before requesting.
Page scroll events → Throttle: Fixed-rate triggering, avoids excessive density.
Window resize → Debounce: Update layout only after resizing stops.
Like/favorite buttons → Throttle ( immediate=true): Immediate feedback on first tap, prevents duplicates.
Verification code sending → Throttle (long wait, e.g., 60s): Enforce one send per 60 seconds.
API Quick Reference
throttle(fn, wait, immediate): fn callback; wait interval ms (default 1000); immediate whether to execute first (default true). Throttles to one execution per fixed time window. debounce(fn, wait, clickId): fn callback; wait delay ms (default 1000); clickId event ID. Debounces to execute after triggers stop.
Summary
ClickUtilencapsulates two classic interaction optimization patterns as concise static methods:
throttle : For "rate limiting" scenarios; the immediate parameter controls whether execution happens at the start or end of the window.
debounce : For "wait for stop" scenarios; the clickId parameter supports multiple independent debounce events.
The underlying dependency on CacheUtil for storing debounce timer IDs demonstrates a collaborative design between utility classes. In any scenario requiring duplicate operation prevention, ClickUtil is the recommended solution.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
51CTO HarmonyOS Developer Community
The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
