Skip to main content
Debouncing is most useful when events trigger expensive work:
  • screenshots or browser automation
  • external API calls
  • LLM/tool runs
  • heavyweight DB/file operations
Instead of starting duplicate work every time, reuse:
  • a recent matching event (past window), or
  • a matching event that is about to be emitted by another caller (future wait), or
  • both (history-first, then short future wait, then emit).
Debouncing in Bubus is built from find(...) + conditional emit(...).

Debounce building blocks

  • past: search recent history (true/false/seconds)
  • future: optionally wait for a matching future emit (true/false/seconds)
  • where / event-field filters: scope matching to the same “work key” (url, account_id, document_id, etc.)
See Find Events for full option semantics.

Pattern 1: Reuse recent completed work (history-only)

Use when “fresh enough” cached results are acceptable.
existing = await bus.find(
    ScreenshotEvent,
    where=lambda e: e.url == url,
    past=10,        # look back 10s
    future=False,   # do not wait
)

event = existing or bus.emit(ScreenshotEvent(url=url))
await event
result = await event.event_result()

Pattern 2: Coalesce concurrent callers (future-only)

Use when many callers may request the same expensive action at the same time. Caller A emits first. Caller B waits briefly for that same event instead of emitting a duplicate.
in_flight = await bus.find(
    ScreenshotEvent,
    where=lambda e: e.url == url,
    past=False,    # skip history
    future=2,      # wait up to 2s for another caller to emit
)

event = in_flight or bus.emit(ScreenshotEvent(url=url))
await event
result = await event.event_result()

Pattern 3: Hybrid debounce (past + short future + emit)

This is the most practical default for expensive endpoints.
  1. Reuse recent match.
  2. If none, wait briefly for someone else to emit.
  3. If still none, emit new work.
event = (
    await bus.find(ScreenshotEvent, where=lambda e: e.url == url, past=10, future=False)
    or await bus.find(ScreenshotEvent, where=lambda e: e.url == url, past=False, future=2)
    or bus.emit(ScreenshotEvent(url=url))
)

await event
result = await event.event_result()

Pattern 4: Keyed helper for repeated use

Wrap the debounce logic once and reuse it for all expensive keyed actions.
async def emit_debounced_screenshot(url: str):
    event = (
        await bus.find(ScreenshotEvent, where=lambda e: e.url == url, past=15, future=False)
        or await bus.find(ScreenshotEvent, where=lambda e: e.url == url, past=False, future=3)
        or bus.emit(ScreenshotEvent(url=url))
    )
    await event
    return await event.event_result()

Important behavior notes

  • find(...) resolves when an event is emitted, not when handlers finish.
  • Always await completion after selecting a debounced event:
    • Python: await event, then await event.event_result()
    • TypeScript: await event.done(), then event.event_result
  • Debouncing scope depends on your match key (where / event-field filters).
    Use the narrowest key that represents “same work.”
  • Debouncing depends on retained history. If history is aggressively trimmed, your past window can become less effective.
See also: