A select checks multiple channels for new value without blocking, the case with a non-blocking operation will be run. A select will often be used with a for and used to process values.

  • select WITHOUT a default: branch will wait for one channel to have a new value/message.
  • select with a default, if no channel has a new value, it will will run the default code.
for {
    select {
    case event := <-dbUpdateEvent:
        if err := sendSSEEvent(w, event); err != nil {
            return // handle error
        }
        // happy path done
    case <-keepAliveTicker.C:
        if err := sendSSEKeepAlive(w); err != nil {
            return // handle error
        }
        // happy path done
    case <-ctx.Done():
        return // handle connection closed error
    }
}

In this sample code we are processing server-sent events.

  • The for is needed to keep sending more events after the first message is sent.
  • It will either:
    • send a db update to the client
      • if err, we return and we’re done looping
    • or a keep alive when our ticker triggers.
      • if err, we return and we’re done looping
    • when the context is closed from a client disconnect, we’re done.
  • However this code has a few issues. if we have an update from dbUpdateEvent at the same time of a keepAliveTicker update, we’ll miss one of the two as the go runtime picks just one case. If we need to set priority (e.g. db update as that will also keep the connection alive) we need split it up into 2 selects, one per dbUpdateEvent and keepAliveTicker (and then check if ctx is closed)