Skip to content

Instantly share code, notes, and snippets.

@leolara
Last active July 25, 2022 09:25
Show Gist options
  • Save leolara/d62b87797b0ef5e418cd to your computer and use it in GitHub Desktop.
Save leolara/d62b87797b0ef5e418cd to your computer and use it in GitHub Desktop.
Golang port of underscore.js
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
}
@alcore
Copy link

alcore commented Oct 30, 2017

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).

func debounceCallable(interval time.Duration, f func()) func() {
	var timer *time.Timer

	return func() {
		if timer == nil {
			timer = time.NewTimer(interval)

			go func() {
				<-timer.C
				timer.Stop()
				timer = nil
				f()
			}()
		} else {
			timer.Reset(interval)
		}
	}
}

@cdreier
Copy link

cdreier commented Dec 21, 2017

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)
			}
		}
	}
}

@illarion
Copy link

illarion commented Feb 11, 2022

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)
}

@metatronz
Copy link

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