awesome-debounce-promise
Debounce async function calls and API requests in JavaScript/TypeScript, resolving only the last promise to avoid race conditions.
Repository Health
Technical Analysis
Awesome Debounce Promise wraps any async function — most commonly an API call — with debouncing behavior tailored for promise-based code. Instead of firing a callback after a delay like classic debounce utilities, it returns a promise per invocation and, by default, resolves only the promise from the most recent call, leaving earlier in-flight calls permanently unresolved. This eliminates the concurrency bugs that occur when a slow earlier request resolves after a newer one and overwrites fresher state (a common issue with search-as-you-type inputs and autosave fields).
The library is a thin composition of three smaller single-purpose packages: debounce-promise for the core debouncing timer logic, and awesome-only-resolves-last-promise for the last-write-wins resolution guarantee. It adds a key option so a single debounced wrapper can maintain independent debounce timers per argument (e.g. per form field or per search key), and ships full first-party TypeScript types. It has no runtime dependency on React, though the README and ecosystem examples (including the companion react-async-hook library) are aimed squarely at React search inputs and autosaving forms.
What You Get
- A single default export,
AwesomeDebouncePromise(func, wait, options), that wraps any async function and returns a debounced version with the same call signature onlyResolvesLastbehavior (on by default) so only the latest call’s promise resolves, preventing stale API responses from clobbering newer ones- A
keyoption to run independent debounce timers keyed by argument, useful for debouncing multiple form fields or search boxes from one wrapper - First-class TypeScript typings with generic inference over the wrapped function’s arguments and return type
- Zero framework dependency — works with plain JS/TS, though it’s commonly paired with React via
react-async-hook - A minimal surface built by composing two smaller focused packages (
debounce-promise,awesome-only-resolves-last-promise) rather than reimplementing debounce logic
Common Use Cases
- Debouncing a search-as-you-type input so the API is only called once the user pauses typing, with only the latest query’s results ever applied to the UI
- Autosaving individual form fields in the background, using the
keyoption to debounce each field independently - Wrapping any API client method to reduce redundant calls triggered by rapid user interaction (typeahead, filters, sliders)
- Preventing race conditions in React components where a fast-typing user could otherwise see an older response overwrite a newer one
Under The Hood
Architecture
The library is a single-file wrapper (src/index.ts) built around a small DebounceCache class that lazily creates and caches debounced function instances, keyed either by a singleton (default) or by a caller-supplied key function for per-argument debouncing. The public AwesomeDebouncePromise factory merges caller options over DefaultOptions, constructs a DebounceCache, and returns a wrapper closure whose call signature mirrors the original function via generic type inference. Actual timing and promise-resolution behavior is delegated outward: debounce-promise provides the debounce timer mechanics, and awesome-only-resolves-last-promise supplies the last-call-wins resolution guarantee applied conditionally when onlyResolvesLast is true. There is effectively no internal state beyond the cache map, so the core abstraction — a debounced function factory — is easy to reason about and would only break if the two upstream microlibraries changed their contracts.
Tech Stack
Written in TypeScript, compiled and bundled with Rollup (rollup-plugin-typescript2, rollup-plugin-commonjs, rollup-plugin-node-resolve, rollup-plugin-peer-deps-external) into CommonJS and ES module builds published under dist/. Runtime dependencies are limited to the two composed microlibraries above plus @types/debounce-promise; there is no framework dependency despite the README’s React-focused examples. Tests run via react-scripts-ts (a Create React App-era TypeScript test runner) with Jest under the hood, and CI was configured through Travis CI (.travis.yml), now inactive along with the rest of the project.
Code Quality
The test suite (src/test.ts) covers the three core behaviors directly — basic debouncing with resolve-last semantics, onlyResolvesLast: false behavior, and per-key debouncing — using a hand-rolled isPromiseResolved race-based assertion helper rather than a dedicated async-test utility. Naming is clear and the file is small enough to review in full; there is no formal error-handling layer since the library doesn’t perform I/O itself, it only orchestrates promises from the wrapped function. TypeScript strict generics are used for the public API surface. There is no linter configuration beyond Prettier for formatting, and no typed error/result handling since none is needed at this scope.
What Makes It Unique
The library’s distinguishing choice is treating debouncing as a promise-resolution problem rather than a timing problem: most debounce utilities (lodash’s _.debounce included) are callback-oriented and leave concurrency handling to the caller. By composing a dedicated “only resolves last promise” microlibrary underneath, this package guarantees stale calls’ promises never resolve at all by default, which is a deliberate, narrow solution to a specific real-world React/API bug class rather than a general-purpose innovation.