Error handling

Работа с библиотекой Shepherd.js предполагает управление пользовательскими турами, состоящими из шагов (steps), привязанных к DOM-элементам. В процессе выполнения тура возможны различные ошибки: отсутствие целевого элемента, конфликт состояний, неправильная конфигурация шагов, асинхронные задержки загрузки интерфейса. Корректная обработка таких ситуаций позволяет избежать сбоев и улучшает пользовательский опыт.


Типы возможных ошибок

1. Отсутствие целевого элемента

Наиболее частая проблема — элемент, указанный в attachTo, отсутствует в DOM в момент показа шага.

{
  attachTo: {
    element: '.missing-element',
    on: 'bottom'
  }
}

Причины:

  • элемент еще не отрисован (асинхронный рендеринг)
  • селектор указан с ошибкой
  • элемент удалён динамически

Последствия:

  • шаг не отображается
  • тур может прерваться или зависнуть

2. Некорректная конфигурация шага

Ошибки в структуре шага:

tour.addStep({
  text: null,
  attachTo: 'invalid-format'
});

Проблемы:

  • text не строка или отсутствует
  • attachTo задан неверно
  • отсутствуют обязательные параметры

3. Ошибки асинхронного поведения

При загрузке данных или элементов через AJAX / SPA-фреймворки:

  • шаг инициируется до появления DOM-элемента
  • состояние интерфейса не соответствует ожидаемому

4. Конфликты состояния тура

  • повторный запуск уже активного тура
  • попытка перейти к следующему шагу после завершения
  • ручное удаление шага во время выполнения

Стратегии обработки ошибок

Проверка наличия элемента перед показом

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

tour.addStep({
  attachTo: {
    element: '.dynamic-element',
    on: 'right'
  },
  beforeShowPromise: function() {
    return new Promise((resolve, reject) => {
      const interval = setInterval(() => {
        const el = document.querySelector('.dynamic-element');
        if (el) {
          clearInterval(interval);
          resolve();
        }
      }, 100);

      setTimeout(() => {
        clearInterval(interval);
        reject('Element not found');
      }, 5000);
    });
  }
});

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

  • предотвращает падение шага
  • даёт контроль над тайм-аутом
  • позволяет логировать ошибки

Глобальная обработка событий тура

Shepherd предоставляет события, на которые можно подписаться:

tour.on('show', (event) => {
  console.log('Показ шага:', event.step.id);
});

tour.on('cancel', () => {
  console.warn('Тур отменён');
});

tour.on('complete', () => {
  console.log('Тур завершён');
});

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

  • логирование неожиданных завершений
  • отслеживание проблемных шагов
  • аналитика поведения пользователей

Обработка ошибок через try/catch

При программном управлении туром:

try {
  tour.start();
} catch (error) {
  console.error('Ошибка запуска тура:', error);
}

Аналогично для переходов:

try {
  tour.next();
} catch (error) {
  console.error('Ошибка перехода:', error);
}

Валидация конфигурации шагов

Перед добавлением шагов полезно валидировать данные:

function validateStep(step) {
  if (!step.text) {
    throw new Error('Step must have text');
  }
  if (!step.attachTo || !step.attachTo.element) {
    throw new Error('attachTo.element is required');
  }
}

Применение:

steps.forEach(step => {
  validateStep(step);
  tour.addStep(step);
});

Защита от повторного запуска тура

if (!tour.isActive()) {
  tour.start();
}

Это предотвращает ошибки состояния и дублирование интерфейса.


Работа с отсутствующими элементами

Пропуск шага

Если элемент не найден, можно автоматически перейти дальше:

tour.addStep({
  attachTo: {
    element: '.optional-element',
    on: 'bottom'
  },
  when: {
    show() {
      const el = document.querySelector('.optional-element');
      if (!el) {
        tour.next();
      }
    }
  }
});

Условное добавление шага

if (document.querySelector('.feature')) {
  tour.addStep({
    text: 'Описание функции',
    attachTo: {
      element: '.feature',
      on: 'top'
    }
  });
}

Логирование ошибок

Централизованный логгер

function logError(message, context) {
  console.error(`[Shepherd Error]: ${message}`, context);
}

Пример использования:

tour.on('cancel', () => {
  logError('Tour cancelled unexpectedly', { step: tour.getCurrentStep() });
});

Интеграция с системами мониторинга

Возможна отправка ошибок в сторонние сервисы:

function reportError(error) {
  fetch('/log', {
    method: 'POST',
    body: JSON.stringify({
      message: error.message,
      stack: error.stack
    })
  });
}

Обработка ошибок пользовательских действий

Кнопки с защитой

buttons: [
  {
    text: 'Далее',
    action() {
      try {
        this.next();
      } catch (e) {
        console.error('Ошибка кнопки:', e);
      }
    }
  }
]

Ограничение взаимодействия

canClickTarget: false

Снижает вероятность ошибок, связанных с некорректными кликами пользователя.


Асинхронные сценарии

Ожидание API-данных

beforeShowPromise() {
  return fetch('/data')
    .then(res => res.json())
    .then(data => {
      if (!data.ready) {
        throw new Error('Data not ready');
      }
    });
}

Обработка отказа Promise

beforeShowPromise() {
  return new Promise((resolve, reject) => {
    someAsyncOperation()
      .then(resolve)
      .catch(err => {
        console.error('Ошибка async:', err);
        reject(err);
      });
  });
}

Предотвращение зависания тура

Тайм-ауты

function waitForElement(selector, timeout = 3000) {
  return new Promise((resolve, reject) => {
    const start = Date.now();

    const check = () => {
      if (document.querySelector(selector)) {
        resolve();
      } else if (Date.now() - start > timeout) {
        reject('Timeout');
      } else {
        requestAnimationFrame(check);
      }
    };

    check();
  });
}

Лучшие практики

  • Всегда проверять наличие DOM-элементов
  • Использовать beforeShowPromise для асинхронных интерфейсов
  • Валидировать конфигурацию шагов
  • Логировать все нестандартные ситуации
  • Избегать жесткой привязки к моменту загрузки страницы
  • Контролировать состояние тура (isActive, getCurrentStep)
  • Обрабатывать пользовательские действия через безопасные обёртки
  • Использовать тайм-ауты для предотвращения зависаний

Частые ошибки и способы их устранения

Ошибка Причина Решение
Шаг не отображается Нет элемента Проверка + beforeShowPromise
Тур зависает Promise не завершён Добавить тайм-аут
Ошибка при next() Тур завершён Проверка isActive()
Дублирование шагов Повторный запуск Контроль состояния
Неправильное позиционирование Неверный селектор Проверка attachTo

Архитектурный подход

Для крупных проектов обработка ошибок должна быть частью общей архитектуры:

  • отдельный модуль управления турами
  • централизованный логгер
  • декларативное описание шагов
  • слой адаптации под асинхронные UI-фреймворки (React, Vue)

Пример структуры:

class TourManager {
  constructor() {
    this.tour = new Shepherd.Tour();
  }

  safeStart() {
    try {
      if (!this.tour.isActive()) {
        this.tour.start();
      }
    } catch (e) {
      this.handleError(e);
    }
  }

  handleError(error) {
    console.error('Tour error:', error);
  }
}

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