11 Nov 2017, 15:00

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!