Lección 16 · 16 de 20
context: tiempo límite y cancelación · Go
Ponga un tiempo límite a una operación y cancélela con context en Go: WithTimeout, WithCancel, ctx.Done() y ctx.Err() con un proveedor lento.

Un `context.Context` viaja de función en función y lleva una orden: «deja lo que estés haciendo». Por convención es el primer parámetro y se llama ctx, como en fetchQuote(ctx, supplier, delay). Las bibliotecas de red y de bases de datos de Go lo aceptan en casi todas sus funciones.
fetchQuote simula un proveedor que tarda delay en dar precio. Su select espera dos cosas: que pase ese tiempo o que se cierre `ctx.Done()`, un canal que se cierra cuando el contexto vence o se cancela. Lo que llegue antes decide. `ctx.Err()` explica el motivo y se envuelve con %w para no perderlo.
`context.WithTimeout` crea un contexto que vence solo, aquí a los 100 ms. FastParts contesta en 10 ms y da su precio; SlowSupply tardaría un segundo, así que gana el plazo y el error es context deadline exceeded. errors.Is(err, context.DeadlineExceeded) lo reconoce aunque esté envuelto.
WithTimeout también devuelve una función cancel, y hay que llamarla siempre, aunque el plazo no llegue a vencer, para liberar sus recursos. Normalmente se escribe defer cancel(); dentro de un bucle se llama al final de cada vuelta, porque un defer esperaría a que terminase main.
`context.WithCancel` no vence solo: se cancela al llamar a cancel(), por ejemplo cuando el cliente cierra la página. Aquí se cancela antes de pedir el precio, así que ctx.Done() ya está cerrado y el error es context canceled. Los tiempos (10, 100 y 1000 ms) están tan separados que la salida es siempre la misma.
Código · main.go
package mainimport ( "context" "errors" "fmt" "time")// fetchQuote simulates a supplier that answers after delay, unless ctx ends first.func fetchQuote(ctx context.Context, supplier string, delay time.Duration) (string, error) { select { case <-time.After(delay): return supplier + " quotes 42.50 EUR", nil case <-ctx.Done(): // closed on timeout or on cancel return "", fmt.Errorf("%s: %w", supplier, ctx.Err()) }}func main() { delays := map[string]time.Duration{"FastParts": 10 * time.Millisecond, "SlowSupply": time.Second} for _, supplier := range []string{"FastParts", "SlowSupply"} { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) quote, err := fetchQuote(ctx, supplier, delays[supplier]) cancel() // always release the context, even if it was not needed fmt.Printf("%-28q err=%v timeout=%t\n", quote, err, errors.Is(err, context.DeadlineExceeded)) } ctx, cancel := context.WithCancel(context.Background()) cancel() // e.g. the customer closed the page before we asked _, err := fetchQuote(ctx, "FastParts", 10*time.Millisecond) fmt.Println("err:", err, "| canceled:", errors.Is(err, context.Canceled))}Salida · go run main.go
"FastParts quotes 42.50 EUR" err=<nil> timeout=false "" err=SlowSupply: context deadline exceeded timeout=true err: FastParts: context canceled | canceled: true
Para recordar
ctxva siempre como primer parámetro.ctx.Done()se cierra al vencer el plazo o al cancelar.ctx.Err()dice por qué:DeadlineExceededoCanceled.- Llame siempre a
cancel(), normalmente condefer. errors.Isreconoce el motivo aunque el error esté envuelto.
Ejercicio
Pida los dos precios a la vez, cada uno en su goroutine y con el mismo contexto de 100 ms, guarde cada resultado en su posición de un slice y escríbalos en orden cuando terminen los dos.