Skip to content

util: implement debounce - #65899

Open
jasnell wants to merge 1 commit into
nodejs:mainfrom
jasnell:jasnell/util-debounce
Open

util: implement debounce#65899
jasnell wants to merge 1 commit into
nodejs:mainfrom
jasnell:jasnell/util-debounce

Conversation

@jasnell

@jasnell jasnell commented Sep 8, 2026

Copy link
Copy Markdown
Member

I found myself using debounce quite a bit recently while testing some recent other additions (quic and dtls testing, perf_hooks improvements, etc). I was using an npm dependency right up until I realized just how generally useful it is to actually have it Just There. So, since it was a holiday and I just felt like it... util.debounce(...)

const fn = util.debounce(() => console.log(123), 1000);

fn();  // starts the timer... will invoke the inner fn
       // after 1 second if fn() is not called again

await setTimeout(500);

fn();  // calling it again before the invocation resets the timer.

I found myself using debounce quite a bit recently while
testing some recent other additions (quic and dtls testing,
perf_hooks improvements, etc). I was using an npm dependency
right up until I realized just how generally useful it is
to actually have it Just There. So, since it was a holiday
and I just felt like it... util.debounce(...)

Signed-off-by: James M Snell <jasnell@gmail.com>
@jasnell jasnell added util Issues and PRs related to the built-in util module. semver-minor PRs that contain new features and should be released in the next minor version. labels Sep 8, 2026
@nodejs-github-bot nodejs-github-bot added the needs-ci PRs that need a full CI run. label Sep 8, 2026
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.48454% with 1 line in your changes missing coverage. Please review.
βœ… Project coverage is 90.17%. Comparing base (72c1fea) to head (459f1a0).
⚠️ Report is 27 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/util/debounce.js 99.45% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #65899    +/-   ##
========================================
  Coverage   90.17%   90.17%            
========================================
  Files         771      772     +1     
  Lines      265097   265291   +194     
  Branches    50355    50393    +38     
========================================
+ Hits       239040   239234   +194     
- Misses      17009    17016     +7     
+ Partials     9048     9041     -7     
Files with missing lines Coverage Ξ”
lib/util.js 100.00% <100.00%> (ΓΈ)
lib/internal/util/debounce.js 99.45% <99.45%> (ΓΈ)

... and 34 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ljharb

ljharb commented Sep 8, 2026

Copy link
Copy Markdown
Member

what's the userland precedent for an AbortSignal-aware debounce implementation?

@jasnell

jasnell commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@ljharb

ljharb commented Sep 8, 2026

Copy link
Copy Markdown
Member

hm, neither very widely used, but at least it's nonzero. i've never seen that usage before, personally.

@bakkot

bakkot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

If you're gonna have options anyway, you might consider having an option for a call that happens at least wait ms after the most recent call to run immediately, with the delay only applied to subsequent calls that happen again before the interval has elapsed (IME this is almost always what I want - debounce is good for handling bursts, but a lot of events are not bursty, and it's annoying to add a delay to those events). Underscore calls this option immediate, lodash leading.

@bakkot

bakkot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

The use of AbortSignal here is a bit weird, and in my opinion wrong.

AbortSignals can only transition from "not aborted" -> "aborted" once, whereas the expectation is that a function like this will be called many times. You can technically dispatch abort events at an already-aborted AbortSignals, but this is an artifact of the user-exposed interface being an EventTarget, which I think was a mistake, and in any case is not how platform-native consumers work (and maybe someday not even userland consumers). A platform-native consumer of AbortSignal always checks if the signal has been aborted before doing any work, and does nothing if so. As such, if you do take an AbortSignal here, I think it has to cancel not just any pending calls but also all future calls (which is incidentally whatp-debounce does, although not deno's debounce).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version. util Issues and PRs related to the built-in util module.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants