context.Context: гайд, который стоило прочитать раньше
Отмена, дедлайны и передача значений. Как перестать игнорировать первый аргумент функции и начать пользоваться им осознанно.
Почти в каждой функции на Go первым аргументом идёт ctx context.Context, и почти все новички первое время его игнорируют — просто пробрасывают дальше, не понимая зачем. Разберёмся, что он на самом деле делает.
Зачем нужен контекст
context.Context решает три задачи: сигнализирует об отмене операции, задаёт дедлайн выполнения и переносит запрос-скоуп значения (например, id трассировки) через цепочку вызовов.
func FetchUser(ctx context.Context, id string) (*User, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
return doRequest(req)
}Отмена и дедлайны
Если пользователь закрыл вкладку или истёк таймаут — контекст позволяет прекратить работу, которая всё равно никому не нужна, вместо того чтобы досчитать её до конца впустую:
ctx, cancel := context.WithTimeout(parent, 3*time.Second)
defer cancel()
result, err := SlowOperation(ctx)
if errors.Is(err, context.DeadlineExceeded) {
log.Println("операция не уложилась в дедлайн")
}defer cancel() нужен всегда, даже если операция успела завершиться — иначе таймер продолжит жить до истечения таймаута и займёт память.
Значения в контексте — с осторожностью
context.WithValue — самая спорная часть API. Используйте её только для request-scoped данных вроде id трассировки, а не для передачи бизнес-параметров функции — для этого есть обычные аргументы.
ctx = context.WithValue(ctx, traceIDKey, generateTraceID())
Как только вы начнёте явно прокидывать контекст первым аргументом в каждую функцию, работающую с сетью, диском или долгими вычислениями — отмена и таймауты перестанут быть источником продовых инцидентов.