Локальная обработка ошибок

Universal Router — это гибкий и минималистичный роутер для JavaScript, который позволяет строить маршрутизацию как на клиентской, так и на серверной стороне. Одной из важнейших особенностей является возможность локальной обработки ошибок на уровне отдельных маршрутов, что обеспечивает изоляцию ошибок и предотвращает их всплытие в глобальный контекст.

Обработка ошибок через error в маршруте

Каждый маршрут в Universal Router может содержать поле action, которое возвращает либо результат маршрута, либо промис. В случае возникновения ошибки внутри action, можно определить локальный обработчик ошибок, используя поле error в объекте маршрута:

const routes = [
  {
    path: '/profile',
    action: async ({ params }) => {
      if (!params.userId) {
        throw new Error('User ID отсутствует');
      }
      return `Профиль пользователя ${params.userId}`;
    },
    error: ({ error, params }) => {
      return `Ошибка на маршруте /profile: ${error.message}`;
    }
  }
];

const router = new UniversalRouter(routes);

router.resolve({ path: '/profile' })
  .then(result => console.log(result))
  .catch(err => console.error('Глобальная ошибка:', err));

Ключевые моменты:

  • error принимает объект с полями error, params, pathname и другими, аналогичными тем, что передаются в action.
  • Локальный обработчик перехватывает ошибки, возникшие внутри action конкретного маршрута.
  • Если error возвращает значение, оно считается результатом маршрута, и глобальный .catch не вызывается.

Использование try/catch внутри action

Иногда требуется более детальная обработка ошибок в самом действии маршрута. Для этого можно использовать стандартный блок try/catch:

{
  path: '/dashboard',
  action: async ({ params }) => {
    try {
      const data = await fetchData(params.dashboardId);
      return renderDashboard(data);
    } catch (err) {
      return `Не удалось загрузить дашборд: ${err.message}`;
    }
  }
}

Этот подход позволяет:

  • Отлавливать ошибки на уровне маршрута без необходимости писать глобальные обработчики.
  • Производить дополнительную обработку, например логирование или преобразование ошибок в пользовательский формат.

Вложенные маршруты и наследование ошибок

Universal Router поддерживает вложенные маршруты, и обработка ошибок может быть локальной для каждого уровня. Вложенный маршрут может иметь собственный error:

const routes = [
  {
    path: '/app',
    children: [
      {
        path: 'settings',
        action: () => { throw new Error('Ошибка в настройках'); },
        error: ({ error }) => `Ошибка в /app/settings: ${error.message}`
      }
    ]
  }
];
  • Ошибка в дочернем маршруте не будет автоматически обработана родительским маршрутом.
  • Родительский обработчик error срабатывает только для ошибок, возникших в его action, если дочерний маршрут не имеет своего error.

Асинхронные ошибки и промисы

Поскольку action может быть асинхронной функцией, важно учитывать, что ошибки в промисах также перехватываются локальным error:

{
  path: '/data',
  action: async () => {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new Error('Ошибка загрузки данных');
    }
    return response.json();
  },
  error: ({ error }) => `Ошибка получения данных: ${error.message}`
}
  • Любая ошибка, выброшенная внутри async action, автоматически передается в локальный error.
  • Это делает локальные обработчики особенно полезными при работе с сетевыми запросами или динамическими загрузками данных.

Принципы организации локальных обработчиков

  1. Минимизировать глобальные catch: использовать локальные error для маршрутов, где можно предсказать возможные ошибки.
  2. Возвращать понятный результат: обработчик должен возвращать значение, которое может быть отображено пользователю или использовано в дальнейшем.
  3. Логировать только необходимые данные: внутренние ошибки можно логировать через console.error или специализированный сервис без вмешательства в поток маршрутизации.
  4. Не повторять действия родителя: дочерний error должен быть автономным, иначе есть риск дублирования сообщений об ошибках.

Практическая схема использования

  • Разделять ошибки по категориям: сетевые, валидационные, логические.
  • Для каждой категории создавать локальный error.
  • Комбинировать с try/catch внутри action, если нужно превратить ошибку в контролируемый результат.

Пример объединения всех подходов:

const routes = [
  {
    path: '/user/:id',
    action: async ({ params }) => {
      try {
        const user = await getUser(params.id);
        if (!user) throw new Error('Пользователь не найден');
        return user;
      } catch (err) {
        throw err; // передаем ошибку локальному error
      }
    },
    error: ({ error, params }) => {
      return `Ошибка загрузки пользователя ${params.id}: ${error.message}`;
    }
  }
];

В этом примере:

  • Любая ошибка в getUser будет перехвачена локальным обработчиком.
  • Сообщение об ошибке формируется с учетом параметров маршрута.
  • Глобальный .catch не вызывается, так как локальный обработчик возвращает результат.

Локальная обработка ошибок в Universal Router позволяет строить устойчивую и предсказуемую маршрутизацию, где каждый маршрут самостоятельно управляет возможными сбоями без нарушения общей логики приложения.