context.Context es la forma estándar en Go de transportar señales de cancelación, deadlines y valores a través de capas de goroutines. Su regla más importante es tan simple como ignorada por los principiantes: el contexto siempre va como primer parámetro de toda función que pueda bloquearse o tardar.
El tipo Context
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
Done() devuelve un canal que se cierra cuando el contexto se cancela o expira. Err() explica el motivo: context.Canceled o context.DeadlineExceeded.
WithCancel: cancelación manual
package main
import (
"context"
"fmt"
"time"
)
func tarea(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("tarea %d cancelada: %vn", id, ctx.Err())
return
default:
fmt.Printf("tarea %d trabajandon", id)
time.Sleep(200 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // buena práctica: siempre hacer defer cancel()
go tarea(ctx, 1)
go tarea(ctx, 2)
time.Sleep(600 * time.Millisecond)
cancel() // señala a todas las goroutines que paren
time.Sleep(100 * time.Millisecond)
}
WithTimeout: límite de tiempo
func buscarConTimeout(query string) ([]string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resultadosCh := make(chan []string, 1)
go func() {
resultados := buscarEnBD(ctx, query) // pasa el contexto a la DB
resultadosCh <- resultados
}()
select {
case r := <-resultadosCh:
return r, nil
case <-ctx.Done():
return nil, fmt.Errorf("búsqueda: %w", ctx.Err())
}
}
WithDeadline: punto de tiempo absoluto
// Deadline absoluto: cancelar a las 14:00:00 deadline := time.Date(2026, 7, 1, 14, 0, 0, 0, time.Local) ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel()
La diferencia entre WithTimeout y WithDeadline es solo de conveniencia: WithTimeout(ctx, d) equivale a WithDeadline(ctx, time.Now().Add(d)).
Pasar el contexto a net/http
El cliente HTTP estándar acepta contextos a través de http.NewRequestWithContext:
func obtenerURL(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("GET %s: %w", url, err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
Pasar el contexto a database/sql
func obtenerUsuario(ctx context.Context, db *sql.DB, id int) (*Persona, error) {
var p Persona
err := db.QueryRowContext(ctx,
"SELECT nombre, edad FROM personas WHERE id = $1", id,
).Scan(&p.Nombre, &p.Edad)
if err != nil {
return nil, fmt.Errorf("obtener usuario %d: %w", id, err)
}
return &p, nil
}
WithValue: transportar valores sin parámetros extra
type claveContexto string
const claveRequestID claveContexto = "request-id"
func handler(w http.ResponseWriter, r *http.Request) {
id := uuid.New().String()
ctx := context.WithValue(r.Context(), claveRequestID, id)
r = r.WithContext(ctx)
siguienteHandler(r)
}
func siguienteHandler(r *http.Request) {
id, _ := r.Context().Value(claveRequestID).(string)
fmt.Println("request-id:", id)
}
Usa tipos privados como clave para evitar colisiones con otros paquetes. No guardes valores que podrían pasarse como parámetros normales de función.
