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