Last active
July 25, 2022 09:25
-
-
Save leolara/d62b87797b0ef5e418cd to your computer and use it in GitHub Desktop.
Golang port of underscore.js
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
func debounceChannel(interval time.Duration, output chan int) chan int { | |
input := make(chan int) | |
go func() { | |
var buffer int | |
var ok bool | |
// We do not start waiting for interval until called at least once | |
buffer, ok = <-input | |
// If channel closed exit, we could also close output | |
if !ok { | |
return | |
} | |
// We start waiting for an interval | |
for { | |
select { | |
case buffer, ok = <-input: | |
// If channel closed exit, we could also close output | |
if !ok { | |
return | |
} | |
case <-time.After(interval): | |
// Interval has passed and we have data, so send it | |
output <- buffer | |
// Wait for data again before starting waiting for an interval | |
buffer, ok = <-input | |
if !ok { | |
return | |
} | |
// If channel is not closed we have more data and start waiting for interval | |
} | |
} | |
}() | |
return input | |
} |
i came up with this solution, input channel and function callback
func debounce(interval time.Duration, input chan string, cb func(arg string)) {
var item string
timer := time.NewTimer(interval)
for {
select {
case item = <-input:
timer.Reset(interval)
case <-timer.C:
if item != "" {
cb(item)
}
}
}
}
No timers or channels solution, that uses power of atomics:
package main
import (
"fmt"
"sync/atomic"
"time"
)
var lastInitRequest atomic.Value
var initializing int32
const DEBOUNCE_DURATION = time.Second * 2
func foo() {
lastInitRequest.Store(time.Now())
if !atomic.CompareAndSwapInt32(&initializing, 0, 1) {
fmt.Println("Already running/sleeping")
return
}
go func() {
defer atomic.StoreInt32(&initializing, 0)
for {
at := lastInitRequest.Load().(time.Time)
now := time.Now()
debounced := at.Add(DEBOUNCE_DURATION)
if debounced.Before(now) {
break
}
sleepDuration := debounced.Sub(now)
if sleepDuration <= 0 {
break
}
fmt.Printf("Sleep for %v\n", sleepDuration)
time.Sleep(sleepDuration)
}
fmt.Println("foo")
}()
}
func main() {
for x := 0; x < 10; x++ {
foo()
time.Sleep(time.Millisecond * 200)
}
time.Sleep(time.Second * 3)
}
func debounce(interval time.Duration, f func()) func() {
var timer *time.Timer
var v atomic.Value
v.Store(timer)
return func() {
swapped := v.CompareAndSwap((*time.Timer)(nil), time.NewTimer(interval))
if swapped {
go func() {
timerr := (v.Load()).(*time.Timer)
<-timerr.C
timerr.Stop()
v.Store((*time.Timer)(nil))
f()
}()
} else {
return
}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Figured I'd drop a closure, not channel, based alternative for those who prefer that approach, closer to the JS world. It's rather straightforward (and easier to reason about imo).