Lodash debounce cancel() limits: what happens to an in-flight async call from the leading edge
0 reputation · 27 Jan 2024, 08:13 UTC
0 reputation · 27 Jan 2024, 08:13 UTC
I'm designing a search input handler around _.debounce (Lodash 4.x) where the debounced function is async and triggers a network request. I plan to call .cancel() when the user navigates away or clears the field.
The documented behavior is clear that .cancel() suppresses any pending trailing-edge invocation. What is less clear to me is the boundary of that guarantee: with leading: true, a call may already be in flight when .cancel() runs, and since Lodash treats the debounced function as synchronous, the returned Promise appears to be outside Lodash's control entirely.
My constraints: I cannot undo a request that has already been sent, and I want to avoid state updates from stale responses after cancellation. I also want to know whether relying on .cancel() alone is sufficient, or whether cancellation of the async work itself must be handled separately (e.g., an AbortController I manage alongside the debounced wrapper).
Specifically:
.cancel() have any defined effect on a Promise returned by a leading-edge invocation that is still pending, or is that Promise guaranteed to settle normally?.cancel() and the leading/trailing options that changes this behavior?AbortController outside the debounced function the intended pattern, or does Lodash offer anything built in for this case?31275 reputation · 27 Jan 2024, 14:36 UTC
When a debounced function is async and invoked with leading: true, the first call runs immediately and returns a Promise. Calling .cancel() afterward clears Lodash's internal timer and prevents any future trailing-edge invocation, but it has no effect on the Promise already returned from the leading-edge call. That Promise will resolve or reject based solely on the async work inside your function.
Version assumption: This behavior matches Lodash 4.x (4.17.x series), which has been stable since its introduction. Confirm the installed version if you are on a custom build or legacy project.
Caution: .flush() has the opposite risk—it immediately invokes a pending trailing call, which can start new async work you may not want during teardown. If you use maxWait, a trailing invocation may be scheduled mid-burst; .cancel() still only prevents future invocations, never in-flight ones.
Side-effect note: AbortController aborts the network request, but other side effects (logging, cache writes) inside your async function will still run unless you check signal.aborted yourself and return early.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.