philiprehberger-debounce

Debounce and throttle decorators for Ruby method calls
Requirements
Installation
Add to your Gemfile:
gem "philiprehberger-debounce"
Or install directly:
gem install philiprehberger-debounce
Usage
require "philiprehberger/debounce"
debouncer = Philiprehberger::Debounce.debounce(wait: 0.5) { |query| search(query) }
debouncer.call('ruby')
debouncer.call('ruby gems') debouncer.call('ruby gems 3')
Throttle
throttler = Philiprehberger::Debounce.throttle(interval: 1.0) { |event| log(event) }
throttler.call('click') throttler.call('click') sleep 1.0
throttler.call('click')
Leading and Trailing Edges
debouncer = Philiprehberger::Debounce.debounce(wait: 0.3, leading: true, trailing: false) do |v|
puts v
end
debouncer = Philiprehberger::Debounce.debounce(wait: 0.3, leading: true, trailing: true) do |v|
puts v
end
Cancel and Flush
debouncer = Philiprehberger::Debounce.debounce(wait: 1.0) { save_draft }
debouncer.call
debouncer.cancel
debouncer.call
debouncer.flush
Max Wait
debouncer = Philiprehberger::Debounce.debounce(wait: 0.5, max_wait: 3.0) do |query|
search(query)
end
Execution Callbacks
debouncer = Philiprehberger::Debounce.debounce(
wait: 0.5,
on_execute: ->(result) { logger.info("Executed: #{result}") },
on_cancel: -> { logger.info('Cancelled') },
on_flush: -> { logger.info('Flushed') }
) { |query| search(query) }
Error Handling
debouncer = Philiprehberger::Debounce.debounce(
wait: 0.5,
on_error: ->(error) { logger.error("debounced job failed: #{error.message}") }
) { |query| search(query) }
on_error: is supported by .debounce, .throttle, .keyed, and .coalesce.
Metrics
debouncer = Philiprehberger::Debounce.debounce(wait: 0.5) { |q| search(q) }
10.times { debouncer.call('test') }
sleep 0.6
debouncer.metrics
debouncer.reset_metrics
Pending Args
debouncer = Philiprehberger::Debounce.debounce(wait: 1.0) { |q| search(q) }
debouncer.call('ruby')
debouncer.pending_args debouncer.cancel
debouncer.pending_args
Keyed Debouncing
keyed = Philiprehberger::Debounce.keyed(wait: 0.5) { |query| search(query) }
keyed.call(:user_1, 'ruby') keyed.call(:user_2, 'python')
keyed.pending_keys keyed.size keyed.cancel(:user_1)
keyed.cancel_all
Rate Limiting
limiter = Philiprehberger::Debounce.rate_limiter(limit: 5, window: 10)
result = limiter.call(:user_1)
limiter.reset(:user_1)
Coalescing
coalescer = Philiprehberger::Debounce.coalesce(wait: 0.5) do |batched_args|
bulk_insert(batched_args)
end
coalescer.call('row1')
coalescer.call('row2')
coalescer.call('row3')
coalescer.flush coalescer.cancel coalescer.pending_count
Last Result
debouncer = Philiprehberger::Debounce.debounce(wait: 0.1) { |x| x.upcase }
debouncer.call('hello')
sleep 0.15
debouncer.last_result
throttler = Philiprehberger::Debounce.throttle(interval: 0.1) { |x| x * 2 }
throttler.call(5)
throttler.last_result
Mixin
class SearchController
include Philiprehberger::Debounce::Mixin
def search(query)
end
debounce_method :search, wait: 0.5
def log_event(event)
end
throttle_method :log_event, interval: 1.0
end
API
Philiprehberger::Debounce
| Method |
Description |
.debounce(wait:, leading: false, trailing: true, max_wait: nil, on_execute: nil, on_cancel: nil, on_flush: nil, on_error: nil, &block) |
Create a debouncer that delays execution |
.throttle(interval:, leading: true, trailing: false, on_execute: nil, on_cancel: nil, on_flush: nil, on_error: nil, &block) |
Create a throttler that limits execution rate |
.keyed(wait:, leading: false, trailing: true, max_wait: nil, on_execute: nil, on_cancel: nil, on_flush: nil, on_error: nil, &block) |
Create a keyed debouncer for per-key debouncing |
.rate_limiter(limit:, window:) |
Create a sliding window rate limiter |
.coalesce(wait:, on_error: nil, &block) |
Create a coalescer that batches arguments |
Debouncer
| Method |
Description |
#call(*args) |
Invoke the debouncer, resetting the timer |
#cancel |
Cancel any pending execution |
#flush |
Execute immediately if pending |
#pending? |
Whether an execution is pending |
#pending_args |
Returns the pending arguments, or nil |
#metrics |
Returns { call_count:, execution_count:, suppressed_count: } |
#reset_metrics |
Resets all metric counters to zero |
#last_result |
Returns the result of the last block execution |
Throttler
| Method |
Description |
#call(*args) |
Invoke the throttler, rate-limited |
#cancel |
Cancel any pending trailing execution |
#flush |
Execute immediately if pending |
#pending? |
Whether a trailing execution is pending |
#pending_args |
Returns the pending arguments, or nil |
#metrics |
Returns { call_count:, execution_count:, suppressed_count: } |
#reset_metrics |
Resets all metric counters to zero |
#last_result |
Returns the result of the last block execution |
KeyedDebouncer
| Method |
Description |
#call(key, *args) |
Invoke the debouncer for a specific key |
#cancel(key) |
Cancel pending execution for a specific key |
#cancel_all |
Cancel all pending executions |
#flush(key) |
Flush pending execution for a specific key immediately |
#flush_all |
Flush all pending keyed debouncers immediately |
#pending_keys |
List keys with pending executions |
#size |
Number of active keyed debouncers currently held (O(1)) |
RateLimiter
| Method |
Description |
#call(key = :default) |
Check rate limit, returns { allowed:, remaining:, retry_after: } |
#reset(key = :default) |
Clear request history for a key |
Coalescer
| Method |
Description |
#call(*args) |
Queue arguments for the next batch |
#flush |
Fire the block immediately with queued args |
#cancel |
Discard all queued arguments |
#pending_count |
Number of queued calls |
#pending_args |
Snapshot of queued argument arrays |
Mixin
| Method |
Description |
.debounce_method(name, wait:, leading: false, trailing: true) |
Debounce an instance method |
.throttle_method(name, interval:, leading: true, trailing: false) |
Throttle an instance method |
Development
bundle install
bundle exec rspec
bundle exec rubocop
Support
If you find this project useful:
⭐ Star the repo
🐛 Report issues
💡 Suggest features
❤️ Sponsor development
🌐 All Open Source Projects
💻 GitHub Profile
🔗 LinkedIn Profile
License
MIT