Effect Курс Catching Errors

Catching Errors

Стратегии перехвата типизированных ошибок в Effect.

Теория

Философия обработки ошибок в Effect

Effect разделяет ошибки на два класса:

  1. Expected Errors (Fail) — ожидаемые, типизированные ошибки в канале E
  2. Unexpected Errors (Defects) — неожиданные дефекты, не представленные в типе

Функции catch* работают только с Expected Errors. Для работы с дефектами используйте catchAllDefect.

Иерархия функций перехвата

                         Catching Functions

          ┌────────────────────┼────────────────────┐
          │                    │                    │
     catchAll             catchSome             catchIf
   (все ошибки)      (выборочно по типу)    (по предикату)
          │                    │
          │              ┌─────┴─────┐
          │              │           │
          │          catchTag    catchTags
          │       (по _tag)    (несколько tags)

    ┌─────┴─────┐
    │           │
catchAllCause  catchCause
(полный Cause) (выборочно)

Типовая трансформация при перехвате

При перехвате ошибки тип Effect меняется:

// До перехвата
Effect<A, E1 | E2 | E3, R>

// После catchAll — канал ошибок становится never или новым типом
Effect<A, never, R>        // если обработчик возвращает успех
Effect<A, E4, R>           // если обработчик может вернуть новую ошибку

// После catchTag("E1") — только E1 убирается из union
Effect<A, E2 | E3, R>      // E1 обработан

Концепция ФП

Recovery как MonadError

В теории типов catch* функции реализуют паттерн MonadError — возможность восстановления из ошибочного состояния:

interface MonadError<F, E> {
  // Создание ошибки
  throwError: <A>(e: E) => F<A>
  
  // Восстановление из ошибки
  catchError: <A>(fa: F<A>, handler: (e: E) => F<A>) => F<A>
}

Effect реализует это через семейство catch* функций.

Изоморфизм с Either

Операция catchAll изоморфна pattern matching на Either:

// Either подход
const handleEither = <E, A, B>(
  either: Either<E, A>,
  onLeft: (e: E) => B,
  onRight: (a: A) => B
): B => either._tag === "Left" ? onLeft(either.left) : onRight(either.right)

// Effect подход
const handleEffect = <E, A, B>(
  effect: Effect<A, E>,
  onError: (e: E) => Effect<B>,
  onSuccess: (a: A) => Effect<B>
): Effect<B> => effect.pipe(
  Effect.matchEffect({ onFailure: onError, onSuccess })
)

Refinement Types

Функции catchTag и catchIf используют refinement types для сужения типа ошибки:

// TypeScript refinement
type Refine<E, Tag extends E["_tag"]> = Extract<E, { _tag: Tag }>
type Exclude<E, Tag extends E["_tag"]> = Exclude<E, { _tag: Tag }>

// catchTag("NetworkError") применяет:
// E = NetworkError | ValidationError | DatabaseError
// → Обработчик получает: NetworkError
// → Остаётся в типе: ValidationError | DatabaseError

API Reference

catchAll

Перехватывает все ожидаемые ошибки и заменяет их результатом обработчика.

Effect.catchAll<A, E, A2, E2, R2>(
  self: Effect<A, E, R>,
  f: (e: E) => Effect<A2, E2, R2>
): Effect<A | A2, E2, R | R2>

Сигнатура типа:

  • Входной Effect: Effect<A, E, R>
  • Обработчик: (e: E) => Effect<A2, E2, R2>
  • Результат: Effect<A | A2, E2, R | R2>

Особенности:

  • Полностью убирает тип E из канала ошибок
  • Может ввести новый тип ошибки E2
  • Обработчик может вернуть успех или новую ошибку

const program = Effect.fail("error").pipe(
  Effect.catchAll((e) => Effect.succeed(`Recovered from: ${e}`))
)
// Effect<string, never, never>

catchAllCause

Перехватывает полный Cause, включая дефекты и прерывания.

Effect.catchAllCause<A, E, A2, E2, R2>(
  self: Effect<A, E, R>,
  f: (cause: Cause<E>) => Effect<A2, E2, R2>
): Effect<A | A2, E2, R | R2>

Когда использовать:

  • Нужен доступ к полной информации о сбое
  • Требуется обработать дефекты или прерывания
  • Логирование с полным контекстом

const program = Effect.die("crash!").pipe(
  Effect.catchAllCause((cause) => {
    if (Cause.isDie(cause)) {
      return Effect.succeed("Recovered from defect")
    }
    return Effect.succeed("Recovered from error")
  })
)

catchSome

Перехватывает выборочно — только если предикат вернул Some.

Effect.catchSome<A, E, A2, E2, R2>(
  self: Effect<A, E, R>,
  pf: (e: E) => Option<Effect<A2, E2, R2>>
): Effect<A | A2, E | E2, R | R2>

Особенности:

  • Возвращает Option<Effect>Some для обработки, None для пропуска
  • Необработанные ошибки остаются в канале E
  • Тип ошибки: E | E2

const program = Effect.fail("not_found").pipe(
  Effect.catchSome((e) =>
    e === "not_found"
      ? Option.some(Effect.succeed("default"))
      : Option.none()
  )
)
// Effect<string, string, never> — string остаётся, т.к. могут быть другие ошибки

catchSomeCause

Аналог catchSome для полного Cause.

Effect.catchSomeCause<A, E, A2, E2, R2>(
  self: Effect<A, E, R>,
  pf: (cause: Cause<E>) => Option<Effect<A2, E2, R2>>
): Effect<A | A2, E | E2, R | R2>

catchIf

Перехватывает ошибки, удовлетворяющие предикату.

Effect.catchIf<A, E, EB extends E, A2, E2, R2>(
  self: Effect<A, E, R>,
  refinement: Refinement<E, EB>,
  f: (e: EB) => Effect<A2, E2, R2>
): Effect<A | A2, Exclude<E, EB> | E2, R | R2>

// Или с простым предикатом
Effect.catchIf<A, E, A2, E2, R2>(
  self: Effect<A, E, R>,
  predicate: Predicate<E>,
  f: (e: E) => Effect<A2, E2, R2>
): Effect<A | A2, E | E2, R | R2>

Особенности:

  • С refinement — точно сужает тип ошибки
  • С predicate — тип остаётся прежним (компилятор не может вывести сужение)

// С refinement (TypeScript понимает сужение типа)
const isNetworkError = (e: AppError): e is NetworkError => 
  e._tag === "NetworkError"

const program = effect.pipe(
  Effect.catchIf(isNetworkError, (e) => 
    Effect.succeed(`Retrying after ${e.code}`)
  )
)

catchTag

Перехватывает ошибки по значению поля _tag (discriminated union).

Effect.catchTag<A, E, Tag extends E["_tag"], A2, E2, R2>(
  self: Effect<A, E, R>,
  tag: Tag,
  f: (e: Extract<E, { _tag: Tag }>) => Effect<A2, E2, R2>
): Effect<A | A2, Exclude<E, { _tag: Tag }> | E2, R | R2>

Особенности:

  • Работает с discriminated unions (ошибки с _tag)
  • Автоматически сужает тип ошибки
  • Самый удобный способ для tagged errors

class NotFound extends Data.TaggedError("NotFound")<{ id: string }> {}
class Forbidden extends Data.TaggedError("Forbidden")<{ reason: string }> {}

const program: Effect.Effect<string, NotFound | Forbidden> = 
  Effect.fail(new NotFound({ id: "123" }))

const handled = program.pipe(
  Effect.catchTag("NotFound", (e) => 
    Effect.succeed(`Item ${e.id} not found, using default`)
  )
)
// Effect<string, Forbidden, never> — NotFound убран из типа!

catchTags

Перехватывает несколько тегов одним вызовом.

Effect.catchTags<A, E, Cases>(
  self: Effect<A, E, R>,
  cases: Cases
): Effect<...>

// Cases — объект с обработчиками для каждого тега
type Cases = {
  [Tag in E["_tag"]]?: (e: Extract<E, { _tag: Tag }>) => Effect<...>
}

Особенности:

  • Удобно для обработки нескольких типов ошибок
  • Все указанные теги убираются из типа
  • Не указанные теги остаются

class NotFound extends Data.TaggedError("NotFound")<{ id: string }> {}
class Forbidden extends Data.TaggedError("Forbidden")<{}> {}
class Timeout extends Data.TaggedError("Timeout")<{}> {}

type ApiError = NotFound | Forbidden | Timeout

const program: Effect.Effect<string, ApiError> = 
  Effect.fail(new NotFound({ id: "123" }))

const handled = program.pipe(
  Effect.catchTags({
    NotFound: (e) => Effect.succeed(`Not found: ${e.id}`),
    Forbidden: () => Effect.succeed("Access denied, using public data")
    // Timeout не обработан — остаётся в типе
  })
)
// Effect<string, Timeout, never>

Стратегии перехвата

Стратегия 1: Полное восстановление (catchAll)

Используйте, когда нужно гарантировать успех независимо от ошибки.


class ConfigError extends Data.TaggedError("ConfigError")<{
  readonly key: string
}> {}

const getConfig = (key: string): Effect.Effect<string, ConfigError> =>
  Effect.fail(new ConfigError({ key }))

// Полное восстановление с default значением
const getConfigSafe = (key: string, defaultValue: string) =>
  getConfig(key).pipe(
    Effect.catchAll(() => Effect.succeed(defaultValue))
  )
// Effect<string, never, never>

Стратегия 2: Точечный перехват (catchTag)

Используйте для обработки конкретных типов ошибок с сохранением остальных.


class ValidationError extends Data.TaggedError("ValidationError")<{
  readonly field: string
  readonly message: string
}> {}

class DatabaseError extends Data.TaggedError("DatabaseError")<{
  readonly query: string
}> {}

class NetworkError extends Data.TaggedError("NetworkError")<{
  readonly url: string
}> {}

type AppError = ValidationError | DatabaseError | NetworkError

const saveUser = (data: unknown): Effect.Effect<string, AppError> =>
  Effect.fail(new ValidationError({ field: "email", message: "Invalid" }))

// Обрабатываем только ValidationError, остальные пробрасываем
const saveUserWithValidation = (data: unknown) =>
  saveUser(data).pipe(
    Effect.catchTag("ValidationError", (e) =>
      Effect.succeed(`Validation failed: ${e.field} - ${e.message}`)
    )
  )
// Effect<string, DatabaseError | NetworkError, never>

Стратегия 3: Условный перехват (catchIf)

Используйте для перехвата на основе свойств ошибки.


class HttpError extends Data.TaggedError("HttpError")<{
  readonly status: number
  readonly body: string
}> {}

const fetchData = (): Effect.Effect<string, HttpError> =>
  Effect.fail(new HttpError({ status: 404, body: "Not found" }))

// Перехватываем только 4xx ошибки
const fetchWithClientErrorHandling = () =>
  fetchData().pipe(
    Effect.catchIf(
      (e): e is HttpError => e._tag === "HttpError" && e.status >= 400 && e.status < 500,
      (e) => Effect.succeed(`Client error ${e.status}: ${e.body}`)
    )
  )

Стратегия 4: Частичный перехват (catchSome)

Используйте когда решение о перехвате зависит от сложной логики.


class RetryableError extends Data.TaggedError("RetryableError")<{
  readonly attempt: number
  readonly maxAttempts: number
}> {}

const operation = (): Effect.Effect<string, RetryableError> =>
  Effect.fail(new RetryableError({ attempt: 1, maxAttempts: 3 }))

// Перехватываем только если есть ещё попытки
const operationWithRetry = () =>
  operation().pipe(
    Effect.catchSome((e) =>
      e.attempt < e.maxAttempts
        ? Option.some(Effect.succeed(`Retrying... (${e.attempt}/${e.maxAttempts})`))
        : Option.none()
    )
  )

Стратегия 5: Множественный перехват (catchTags)

Используйте для комплексной обработки нескольких типов ошибок.


class AuthError extends Data.TaggedError("AuthError")<{}> {}
class RateLimitError extends Data.TaggedError("RateLimitError")<{ 
  retryAfter: number 
}> {}
class ServerError extends Data.TaggedError("ServerError")<{ 
  code: number 
}> {}

type ApiError = AuthError | RateLimitError | ServerError

const callApi = (): Effect.Effect<string, ApiError> =>
  Effect.fail(new RateLimitError({ retryAfter: 60 }))

const callApiWithRecovery = () =>
  callApi().pipe(
    Effect.catchTags({
      AuthError: () => 
        Effect.succeed("Please login again"),
      RateLimitError: (e) => 
        Effect.succeed(`Rate limited, retry after ${e.retryAfter}s`)
      // ServerError пробрасывается дальше
    })
  )
// Effect<string, ServerError, never>

Примеры

Пример 1: REST API с полной обработкой ошибок


// Домен ошибок
class NotFoundError extends Data.TaggedError("NotFoundError")<{
  readonly resource: string
  readonly id: string
}> {}

class ValidationError extends Data.TaggedError("ValidationError")<{
  readonly errors: ReadonlyArray<{ field: string; message: string }>
}> {}

class UnauthorizedError extends Data.TaggedError("UnauthorizedError")<{
  readonly reason: string
}> {}

class InternalError extends Data.TaggedError("InternalError")<{
  readonly message: string
}> {}

type ApiError = NotFoundError | ValidationError | UnauthorizedError | InternalError

// HTTP Response
interface ApiResponse<T> {
  readonly status: number
  readonly body: T
}

// Симуляция API call
const fetchUser = (id: string): Effect.Effect<{ name: string }, ApiError> =>
  Effect.fail(new NotFoundError({ resource: "User", id }))

// Конвертация в HTTP response
const toHttpResponse = <T>(
  effect: Effect.Effect<T, ApiError>
): Effect.Effect<ApiResponse<unknown>, never> =>
  effect.pipe(
    Effect.map((data) => ({ status: 200, body: data })),
    Effect.catchTags({
      NotFoundError: (e) => 
        Effect.succeed({ 
          status: 404, 
          body: { error: `${e.resource} with id ${e.id} not found` } 
        }),
      ValidationError: (e) => 
        Effect.succeed({ 
          status: 400, 
          body: { error: "Validation failed", details: e.errors } 
        }),
      UnauthorizedError: (e) => 
        Effect.succeed({ 
          status: 401, 
          body: { error: e.reason } 
        }),
      InternalError: (e) => 
        Effect.succeed({ 
          status: 500, 
          body: { error: "Internal server error" } 
        })
    })
  )

// Использование
const handleRequest = (userId: string) =>
  toHttpResponse(fetchUser(userId))

Effect.runPromise(handleRequest("123")).then(console.log)
// { status: 404, body: { error: 'User with id 123 not found' } }

Пример 2: Цепочка fallback-ов


class CacheError extends Data.TaggedError("CacheError")<{}> {}
class DbError extends Data.TaggedError("DbError")<{}> {}
class ServiceError extends Data.TaggedError("ServiceError")<{}> {}

type DataError = CacheError | DbError | ServiceError

// Источники данных
const fromCache = (key: string): Effect.Effect<string, CacheError> =>
  Effect.fail(new CacheError())

const fromDatabase = (key: string): Effect.Effect<string, DbError> =>
  Effect.fail(new DbError())

const fromService = (key: string): Effect.Effect<string, ServiceError> =>
  Effect.succeed(`Data for ${key} from service`)

// Цепочка fallback-ов с разными типами ошибок
const getData = (key: string): Effect.Effect<string, ServiceError> =>
  fromCache(key).pipe(
    Effect.catchTag("CacheError", () => fromDatabase(key)),
    Effect.catchTag("DbError", () => fromService(key))
  )

Effect.runPromise(getData("user:123")).then(console.log)
// Data for user:123 from service

Пример 3: Логирование ошибок с продолжением


class ProcessingError extends Data.TaggedError("ProcessingError")<{
  readonly itemId: string
  readonly reason: string
}> {}

// Обработка с логированием ошибок
const processItem = (itemId: string): Effect.Effect<string, ProcessingError> =>
  Math.random() > 0.5
    ? Effect.succeed(`Processed: ${itemId}`)
    : Effect.fail(new ProcessingError({ itemId, reason: "Random failure" }))

// Обработка batch с логированием ошибок
const processBatch = (
  itemIds: ReadonlyArray<string>
): Effect.Effect<ReadonlyArray<string>> =>
  Effect.forEach(itemIds, (itemId) =>
    processItem(itemId).pipe(
      Effect.catchAll((error) =>
        Console.error(`Failed to process ${error.itemId}: ${error.reason}`).pipe(
          Effect.as(`SKIPPED: ${itemId}`)
        )
      )
    )
  )

Effect.runPromise(processBatch(["a", "b", "c", "d", "e"])).then(console.log)

Пример 4: Перехват с трансформацией в другой тип ошибки


// Внутренние ошибки (детальные)
class PostgresError extends Data.TaggedError("PostgresError")<{
  readonly code: string
  readonly detail: string
}> {}

class RedisError extends Data.TaggedError("RedisError")<{
  readonly command: string
}> {}

type InfraError = PostgresError | RedisError

// Публичные ошибки (абстрактные)
class StorageError extends Data.TaggedError("StorageError")<{
  readonly operation: string
  readonly message: string
}> {}

// Маппинг внутренних ошибок в публичные
const mapToStorageError = (
  operation: string
) => <A, R>(
  effect: Effect.Effect<A, InfraError, R>
): Effect.Effect<A, StorageError, R> =>
  effect.pipe(
    Effect.catchTags({
      PostgresError: (e) =>
        Effect.fail(new StorageError({ 
          operation, 
          message: `Database error: ${e.code}` 
        })),
      RedisError: (e) =>
        Effect.fail(new StorageError({ 
          operation, 
          message: `Cache error on ${e.command}` 
        }))
    })
  )

// Использование
const saveToDb = (): Effect.Effect<void, PostgresError> =>
  Effect.fail(new PostgresError({ code: "23505", detail: "Duplicate key" }))

const publicSave = saveToDb().pipe(mapToStorageError("save"))
// Effect<void, StorageError, never>

Пример 5: catchSome для условного восстановления


class TemporaryError extends Data.TaggedError("TemporaryError")<{
  readonly retryAfter: Duration.Duration
}> {}

class PermanentError extends Data.TaggedError("PermanentError")<{
  readonly reason: string
}> {}

type ServiceError = TemporaryError | PermanentError

const callExternalService = (): Effect.Effect<string, ServiceError> =>
  Effect.fail(new TemporaryError({ retryAfter: Duration.seconds(5) }))

// Восстанавливаемся только от временных ошибок
const callWithPartialRecovery = () =>
  callExternalService().pipe(
    Effect.catchSome((error) => {
      // Восстанавливаемся только от временных ошибок
      if (error._tag === "TemporaryError") {
        return Option.some(
          Effect.succeed(`Will retry after ${Duration.toMillis(error.retryAfter)}ms`)
        )
      }
      // Permanent ошибки не перехватываем
      return Option.none()
    })
  )
// Effect<string, ServiceError, never> — тип ошибки сохраняется

Пример 6: catchAllCause для полного контроля


class BusinessError extends Data.TaggedError("BusinessError")<{
  readonly code: string
}> {}

const riskyOperation = (): Effect.Effect<string, BusinessError> =>
  Effect.die(new Error("Unexpected crash!"))

// Полный контроль над всеми причинами сбоя
const safeOperation = () =>
  riskyOperation().pipe(
    Effect.catchAllCause((cause) =>
      Effect.gen(function* () {
        // Логируем полную причину
        yield* Console.error(`Full cause: ${Cause.pretty(cause)}`)
        
        // Анализируем тип причины
        if (Cause.isFailType(cause)) {
          const error = cause.error
          return `Business error: ${error.code}`
        }
        
        if (Cause.isDie(cause)) {
          const defect = cause.defect
          return `System crash: ${defect instanceof Error ? defect.message : String(defect)}`
        }
        
        if (Cause.isInterruptType(cause)) {
          return "Operation was interrupted"
        }
        
        // Комбинированные причины
        const allFailures = [...Cause.failures(cause)]
        const allDefects = [...Cause.defects(cause)]
        
        return `Multiple issues: ${allFailures.length} errors, ${allDefects.length} defects`
      })
    )
  )

Effect.runPromise(safeOperation()).then(console.log)
// System crash: Unexpected crash!

Упражнения

Упражнение

Базовый catchAll

Легко

class DivisionByZero extends Data.TaggedError("DivisionByZero")<{}> {}

const divide = (a: number, b: number): Effect.Effect<number, DivisionByZero> =>
  b === 0
    ? Effect.fail(new DivisionByZero())
    : Effect.succeed(a / b)

// TODO: Реализуйте safeDivide, который возвращает 0 при делении на 0
const safeDivide = (a: number, b: number): Effect.Effect<number, never> => {
  // Ваш код
}
Упражнение

catchTag для discriminated union

Легко

class NotFound extends Data.TaggedError("NotFound")<{ id: string }> {}
class Unauthorized extends Data.TaggedError("Unauthorized")<{}> {}
class ServerError extends Data.TaggedError("ServerError")<{ message: string }> {}

type ApiError = NotFound | Unauthorized | ServerError

const fetchResource = (id: string): Effect.Effect<string, ApiError> =>
  Effect.fail(new NotFound({ id }))

// TODO: Обработайте только NotFound, вернув "default"
// Остальные ошибки должны пробрасываться
const fetchWithDefault = (
  id: string
): Effect.Effect<string, Unauthorized | ServerError> => {
  // Ваш код
}
Упражнение

catchTags для множественной обработки

Средне

class InvalidInput extends Data.TaggedError("InvalidInput")<{
  readonly field: string
  readonly value: unknown
}> {}

class Timeout extends Data.TaggedError("Timeout")<{
  readonly ms: number
}> {}

class ConnectionLost extends Data.TaggedError("ConnectionLost")<{
  readonly endpoint: string
}> {}

class UnknownError extends Data.TaggedError("UnknownError")<{
  readonly cause: unknown
}> {}

type ProcessError = InvalidInput | Timeout | ConnectionLost | UnknownError

const process = (data: unknown): Effect.Effect<string, ProcessError> =>
  Effect.fail(new Timeout({ ms: 5000 }))

// TODO: Используйте catchTags для обработки InvalidInput и Timeout
// ConnectionLost и UnknownError должны пробрасываться
const processWithRecovery = (
  data: unknown
): Effect.Effect<string, ConnectionLost | UnknownError> => {
  // Ваш код
}
Упражнение

catchIf с refinement

Средне

class HttpError extends Data.TaggedError("HttpError")<{
  readonly status: number
  readonly body: string
}> {}

const callApi = (): Effect.Effect<string, HttpError> =>
  Effect.fail(new HttpError({ status: 503, body: "Service Unavailable" }))

// TODO: Перехватите только 5xx ошибки (status >= 500)
// 4xx ошибки должны пробрасываться
const callApiWithServerErrorRecovery = (): Effect.Effect<string, HttpError> => {
  // Ваш код
}
Упражнение

Построение error boundary

Сложно

// Создайте универсальный error boundary, который:
// 1. Логирует все ошибки
// 2. Конвертирует ожидаемые ошибки в пользовательские сообщения
// 3. Конвертирует дефекты в generic "Internal error"
// 4. Возвращает fallback значение

interface ErrorBoundaryConfig<A, E> {
  readonly errorToMessage: (e: E) => string
  readonly fallback: A
  readonly onError?: (message: string) => Effect.Effect<void>
}

const createErrorBoundary = <A, E>(
  config: ErrorBoundaryConfig<A, E>
) => (
  effect: Effect.Effect<A, E>
): Effect.Effect<{ value: A; error?: string }, never> => {
  // Ваш код
}
Упражнение

Retry с экспоненциальным backoff и умным перехватом

Сложно

class RetryableError extends Data.TaggedError("RetryableError")<{
  readonly attempt: number
  readonly reason: string
}> {}

class FatalError extends Data.TaggedError("FatalError")<{
  readonly reason: string
}> {}

type OperationError = RetryableError | FatalError

// TODO: Создайте функцию, которая:
// 1. Retry только RetryableError с экспоненциальным backoff
// 2. FatalError сразу пробрасывает
// 3. После N попыток конвертирует RetryableError в FatalError
// 4. Логирует каждую попытку

const withSmartRetry = <A>(
  effect: Effect.Effect<A, OperationError>,
  maxAttempts: number
): Effect.Effect<A, FatalError> => {
  // Ваш код
}

Резюме

ФункцияПерехватываетСужение типаКогда использовать
catchAllВсе ошибкиE → never/E2Полное восстановление
catchAllCauseВесь CauseE → never/E2Дефекты + прерывания
catchSomeВыборочно (Option)E сохраняетсяУсловное восстановление
catchIfПо предикатуС refinementФильтрация по свойствам
catchTagПо _tagE \ TagTagged errors
catchTagsНесколько _tagE \ TagsМножественная обработка

Ключевые принципы:

  • catch* работают только с Expected Errors (Fail)
  • catchTag — самый удобный способ для tagged errors
  • Используйте catchTags для комплексной обработки
  • catchAllCause даёт доступ к дефектам и прерываниям