Select
Promise.race vs select
Section titled “Promise.race vs select”In TypeScript Promise.race accepts an array of Promises and resolves with whichever settles first:
const result = await Promise.race([ fetch('/api/primary'), fetch('/api/backup'),]);In Go, select does the same for channels — it waits on multiple channel operations and proceeds with the first one that is ready:
select {case msg := <-ch1: fmt.Println("from ch1:", msg)case msg := <-ch2: fmt.Println("from ch2:", msg)}select blocks until at least one case can proceed. If multiple cases are ready simultaneously, Go picks one uniformly at random — keeping the scheduling fair but non-deterministic.
Basic select
Section titled “Basic select”// TypeScript: Promise.race for the first resultasync function withTimeout<T>( promise: Promise<T>, ms: number): Promise<T> { const timeout = new Promise<never>((_, reject) => setTimeout(() => reject(new Error("timeout")), ms) ); return Promise.race([promise, timeout]);}// Go: select picks the first ready channelpackage main
import "fmt"
func main() { ch1 := make(chan string, 1) ch2 := make(chan string, 1)
ch1 <- "one" ch2 <- "two"
// Use separate selects so output is deterministic select { case msg := <-ch1: fmt.Println("Received from ch1:", msg) } select { case msg := <-ch2: fmt.Println("Received from ch2:", msg) }}default case — non-blocking poll
Section titled “default case — non-blocking poll”Add a default branch to make select return immediately when no channel is ready instead of blocking:
// TypeScript: non-blocking check via Promise.race with null fallbackasync function tryReceive(): Promise<string | null> { return Promise.race([ getMessage(), Promise.resolve(null), // resolves immediately ]);}// Go: non-blocking select with defaultpackage main
import "fmt"
func main() { ch := make(chan string, 1)
select { case msg := <-ch: fmt.Println("Got:", msg) default: fmt.Println("No message available") // runs immediately }
ch <- "hello"
select { case msg := <-ch: fmt.Println("Got:", msg) default: fmt.Println("No message available") }}select with context cancellation
Section titled “select with context cancellation”The most common real-world use of select is to simultaneously listen for incoming work and a cancellation signal from a context.Context:
// TypeScript: AbortController for cooperative cancellationasync function processItems( items: string[], signal: AbortSignal) { for (const item of items) { if (signal.aborted) break; await processItem(item); }}// Go: select listening to both a jobs channel and ctx.Done()package main
import ( "context" "fmt" "sync")
func worker(ctx context.Context, jobs <-chan int, wg *sync.WaitGroup) { defer wg.Done() for { select { case j, ok := <-jobs: if !ok { return // channel was closed — no more work } fmt.Println("Processing job:", j) case <-ctx.Done(): fmt.Println("Worker cancelled:", ctx.Err()) return } }}package main
import "fmt"
func main() { ch1 := make(chan string, 1) ch2 := make(chan string, 1)
ch1 <- "hello" ch2 <- "world"
// Receive from ch1 first (deterministic: separate selects) select { case msg := <-ch1: fmt.Println("ch1:", msg) }
// Receive from ch2 select { case msg := <-ch2: fmt.Println("ch2:", msg) }
// default: non-blocking — ch1 is empty now select { case msg := <-ch1: fmt.Println("extra:", msg) default: fmt.Println("no more messages") }}Loading Go runtime (first run only, ~8 MB)…