-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from ratanphayade/master
Master
- Loading branch information
Showing
6 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# ignore IDE files | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Debouncer | ||
|
||
Debounce and throttle are two similar (but different!) techniques to control how many times we allow a function to be | ||
executed over time. Debounce is used when you have to consider only the final state. | ||
|
||
A typical example for this is auto submit / auto suggestions. In these cases rather than making a request to server for | ||
suggestions, it's recommended to wait for some time. If the end user is still typing then ignore the previous request | ||
and consider the latest value. | ||
|
||
This can be very well adapted in multiple places to reduce the load on the system. | ||
|
||
## Getting Started | ||
install `debouncer` | ||
``` | ||
$ go get -u -v github.com/ratanphayade/debouncer | ||
``` | ||
|
||
### Using Debouncer | ||
|
||
#### Create an instance | ||
To create an instance | ||
```go | ||
d := debouncer.NewDebouncer(ttl) | ||
``` | ||
Here ttl is the interval until which the event has to wait before performing action. In case before ttl another event | ||
received then, previous event will be ignored, and it'll again wait for a duration specified by ttl before performing the | ||
action | ||
|
||
*Note: One instance can handle only action. If required to have multiple action then, multiple instance has to be created* | ||
|
||
#### Start the action listener | ||
Action should be of type | ||
```go | ||
type Action func(ctx context.Context, value interface{}) error | ||
``` | ||
|
||
Once we have the action defined, we can start the action listener | ||
```go | ||
d.Do(ctx, func(_ context.Context, val interface{}) error { | ||
counter++ | ||
result = result + val.(int) | ||
return nil | ||
}) | ||
``` | ||
|
||
#### Triggering the event | ||
Calling below method with every event will invoke the debounce action | ||
```go | ||
d.TriggerAction(value) | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package debouncer | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// Action method signature of the action | ||
// which has to be performed on event | ||
type Action func(ctx context.Context, value interface{}) error | ||
|
||
type Debouncer struct { | ||
// Input represents the change event | ||
Input chan interface{} | ||
|
||
// Interval represent the max time it should wait | ||
// before performing the Action | ||
Interval time.Duration | ||
|
||
// once will be used to ensure that the Do method | ||
// is called only once per deboubncer instance | ||
// as a single debounce can take care of only one operation | ||
// calling it multiple times might cause inconsistencies | ||
once sync.Once | ||
} | ||
|
||
// NewDebouncer creates a new instance of debouncer | ||
// this will create an unbuffered channel to capture a event | ||
func NewDebouncer(interval time.Duration) *Debouncer { | ||
return &Debouncer{ | ||
Input: make(chan interface{}), | ||
Interval: interval, | ||
} | ||
} | ||
|
||
// TriggerAction records an event to perform the Action provide | ||
// this will add given value to the input channel as notification | ||
// for debouncer | ||
func (d *Debouncer) TriggerAction(val interface{}) { | ||
d.Input <- val | ||
} | ||
|
||
// Do will run the debounce in a go routine | ||
// and it'll make sure that its been invoked only once | ||
// as multiple action can not fall under same config | ||
func (d *Debouncer) Do(ctx context.Context, action Action) { | ||
// ensure debouncing is started only once per instance | ||
d.once.Do(func() { | ||
go d.debounce(ctx, action) | ||
}) | ||
} | ||
|
||
// debounce will make sure that the given action is not performed repeatedly | ||
// if its triggered multiple times within a given interval | ||
func (d *Debouncer) debounce(ctx context.Context, action Action) { | ||
var ( | ||
// hasEvent represents of there is a valid event received | ||
// this will help avoid unnecessary triggering if the method | ||
hasEvent bool | ||
|
||
// value holds the latest input received | ||
value interface{} | ||
|
||
timer = time.NewTimer(d.Interval) | ||
) | ||
|
||
for { | ||
select { | ||
// if there is an event then reset the timer | ||
// and update the hasEvent to true representing | ||
// to trigger the function once the timer ends | ||
case value = <-d.Input: | ||
hasEvent = true | ||
timer.Reset(d.Interval) | ||
|
||
// if the timer ends there is a valid event | ||
// then call the Action provider | ||
case <-timer.C: | ||
if hasEvent { | ||
_ = action(ctx, value) | ||
hasEvent = false | ||
} | ||
|
||
// if the application is being terminated | ||
// then stop the debouncing | ||
case <-ctx.Done(): | ||
return | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package debouncer_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ratanphayade/debouncer" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestDebounce_Do(t *testing.T) { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
// initialize the debouncer with the interval | ||
// until which the trigger will wait before executing the action | ||
d := debouncer.NewDebouncer(100 * time.Millisecond) | ||
var ( | ||
counter int | ||
result int | ||
) | ||
|
||
// start the action listener | ||
d.Do(ctx, func(_ context.Context, val interface{}) error { | ||
counter++ | ||
result = result + val.(int) | ||
return nil | ||
}) | ||
|
||
// triggering multiple action events | ||
for i := 0; i < 5; i++ { | ||
d.TriggerAction(i) | ||
} | ||
|
||
time.Sleep(500 * time.Millisecond) | ||
cancel() | ||
|
||
assert.Equal(t, 1, counter) | ||
assert.Equal(t, 4, result) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/ratanphayade/debouncer | ||
|
||
go 1.13 | ||
|
||
require github.com/stretchr/testify v1.6.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= | ||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |