Replicating JavaScripts setTimeout and setInterval in Go

In JavaScript we have two inbuilt functions for doing some work at some given point in the future. Firstly setTimeout; this allows us to run a function after a given period of time in milliseconds. We also have setInterval which allows us to run a function every x milliseconds until the interval is cancelled with clearInterval. You can find a solid overview of timers in JavaScript on MDN here.

For those unfamiliar with the two functions, let's see how they look (in ES5):

var timeout = 1000; // 1 second

setTimeout(function () {
console.log("This will happen after " + timeout + "milliseconds");
});

var interval = 500; // 0.5 seconds

var anInterval = setInterval(function () {
console.log("This will happen after " + timeout + "seconds");
});

Recently I have been wanting to achieve the same thing in Go on a small side project I am working on. As such I thought I would share my findings with you all!

Replicating setInterval #

setInterval is arguably the more complicated of the two functions to implement. The basic premise is we setup a new ticker from Go's time package. The NewTicker function creates a channel which we can select over (see here for an overview of the select feature in Go), which passes a signal to the channel every x milliseconds, calling the arbitrary passed in function. We also select over the clear channel. This can in turn clear the interval when we pass a boolean value to it.


func setInterval(someFunc func(), milliseconds int, async bool) chan bool {

// How often to fire the passed in function
// in milliseconds
interval := time.Duration(milliseconds) * time.Millisecond

// Setup the ticket and the channel to signal
// the ending of the interval
ticker := time.NewTicker(interval)
clear := make(chan bool)

// Put the selection in a go routine
// so that the for loop is none blocking
go func() {
for {

select {
case <-ticker.C:
if async {
// This won't block
go someFunc()
} else {
// This will block
someFunc()
}
case <-clear:
ticker.Stop()
return
}

}
}()

// We return the channel so we can pass in
// a value to it to clear the interval
return clear

}

func main() {

// A counter for the number of times we print
printed := 0

// We call set interval to print Hello World forever
// every 1 second
interval := setInterval(func() {
fmt.Println("Hello World")
printed++
}, 1000, false)

// If we wanted to we had a long running task (i.e. network call)
// we could pass in true as the last argument to run the function
// as a goroutine

// Some artificial work here to wait till we've printed
// 5 times
for {
if printed == 5 {
// Stop the ticket, ending the interval go routine
stop <- true
return
}
}

}

Replicating setTimeout #

Replicating JavaScripts setTimeout is slightly more straight forward. We can leverage the time package's AfterFunc function which fires off goroutine that will run a function after a given period of time. We could do this using code like this:


func setTimeout(someFunc func(), milliseconds int) {

timeout := time.Duration(milliseconds) * time.Millisecond

// This spawns a goroutine and therefore does not block
time.AfterFunc(timeout, someFunc)

}

func main() {

printed := false
print := func() {
fmt.Println("This will print after x milliseconds")
printed = true
}

// Make the timeout print after 5 seconds
setTimeout(print, 5000)

fmt.Println("This will print straight away")

// Wait until it's printed our function string
// before we close the program
for {
if printed {
return
}
}

}

Hopefully this has given some insight on how we might achieve similar functionality in Go to JavaScripts setInterval and setTimeout functionality. If you see a potential problem, or have ideas about a better solution I would love to hear them!

Published