Context

The context package provides a mechanism to manage the flow of execution across API boundaries. It can be used for canceling operations, setting deadlines and propagating values.

In node.js there are AsyncLocalStorage and AbortSignal.

Go's API is pretty dump, no magic. You have to passdown Context to child function calls explicitly.

It's worth mentioning that the API of the context package takes an immutable approach. When creating a new context with context.WithTimeout, context.WithDeadline, or context.WithValue, a new context is returned, leaving the original context unchanged, which makes the code more predictable.

package main

import (
	"context"
	"fmt"
	"time"
)

func doWork(ctx context.Context) {
	select {
	case <-ctx.Done():
		fmt.Println("context canceled")
	case <-time.After(time.Second):
		fmt.Println("work done")
	}
}

func foo(ctx context.Context) {
	doWork(ctx)
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
	defer cancel()
	go foo(ctx)
	<-ctx.Done()
}

example: limit http request processing time

package main

import (
	"fmt"
	"net/http"
	"time"
)

func TimeoutMiddleware(timeout time.Duration) func(http.HandlerFunc) http.HandlerFunc {
	return func(next http.HandlerFunc) http.HandlerFunc {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			done := make(chan bool, 1)
			go func() {
				next(w, r)
				done <- true
			}()

			select {
			case <-time.After(timeout):
				http.Error(w, "Request Timeout", http.StatusRequestTimeout)
			case <-done:
			}
		})
	}
}

func Handler(w http.ResponseWriter, r *http.Request) {
	time.Sleep(2 * time.Second)
	fmt.Fprintf(w, "Served on time")
}

func main() {
	http.HandleFunc("GET /timeout/1", TimeoutMiddleware(1*time.Second)(Handler))
	http.HandleFunc("GET /timeout/3", TimeoutMiddleware(3*time.Second)(Handler))
	if err := http.ListenAndServe(":3000", nil); err != nil {
		panic(err)
	}
}