Channels
Promises and callbacks vs channels
Section titled “Promises and callbacks vs channels”In JavaScript when you need to pass a result from an async operation to another piece of code, you use Promises or callbacks:
function fetchUser(id: number): Promise<User> { return new Promise((resolve) => { // ... async work ... resolve(user); });}const user = await fetchUser(1); // await the PromiseIn Go, channels are typed first-class pipes for sending values between goroutines. A channel is both the communication and synchronization mechanism:
ch := make(chan User) // create a channel for User valuesgo func() { ch <- user }() // send into the channeluser := <-ch // receive from the channel (blocks until a value arrives)Unbuffered channels
Section titled “Unbuffered channels”An unbuffered channel has no queue. Send and receive must happen simultaneously — the sender blocks until a receiver is ready, and the receiver blocks until a sender sends. This is a rendezvous.
// TypeScript: Promise for a single future valuefunction getResult(): Promise<number> { return new Promise(resolve => { setTimeout(() => resolve(42), 100); });}const result = await getResult();console.log(result); // 42// Go: unbuffered channel — sender and receiver sync uppackage main
import "fmt"
func getResult(ch chan<- int) { ch <- 42 // send; blocks until someone receives}
func main() { ch := make(chan int) // unbuffered go getResult(ch) result := <-ch // receive; blocks until sender sends fmt.Println(result) // 42}Buffered channels
Section titled “Buffered channels”A buffered channel has an internal queue. The sender does not block until the buffer is full, and the receiver does not block as long as the buffer has values. This decouples the producer from the consumer.
// TypeScript: simulate a buffer with an arrayconst queue: number[] = [];function enqueue(val: number) { queue.push(val); // does not block}enqueue(1); enqueue(2); enqueue(3);// must drain it yourself// Go: buffered channel with a queue of 3package main
import "fmt"
func main() { ch := make(chan int, 3) // buffer capacity 3
ch <- 1 // does not block (space available) ch <- 2 ch <- 3 // ch <- 4 // would block — buffer full
fmt.Println(<-ch) // 1 fmt.Println(<-ch) // 2 fmt.Println(<-ch) // 3}Range over a closed channel
Section titled “Range over a closed channel”Use for range to drain a channel until it is closed. The producer closes the channel to signal it is done sending, analogous to returning from an async generator.
// TypeScript: async generator as an iterable streamasync function* generate() { yield 1; yield 2; yield 3;}for await (const val of generate()) { console.log(val);}// Go: range over a channel closed by the producerpackage main
import ( "fmt" "sync")
func produce(ch chan<- int, wg *sync.WaitGroup) { defer wg.Done() for i := 1; i <= 5; i++ { ch <- i }}
func main() { ch := make(chan int, 5) var wg sync.WaitGroup wg.Add(1) go produce(ch, &wg) wg.Wait() close(ch) // signal: no more values
for v := range ch { // drains until channel is closed fmt.Println(v) }}package main
import ( "fmt" "sync")
func producer(ch chan<- int, wg *sync.WaitGroup) { defer wg.Done() for i := 1; i <= 5; i++ { ch <- i }}
func main() { ch := make(chan int, 5) var wg sync.WaitGroup wg.Add(1) go producer(ch, &wg) wg.Wait() close(ch) for v := range ch { fmt.Println("Received:", v) }}Loading Go runtime (first run only, ~8 MB)…