Асинхронные шаги

Driver.js — это библиотека для создания интерактивных пошаговых руководств по интерфейсу веб-приложений. Одной из её мощных возможностей является поддержка асинхронных шагов, что позволяет динамически управлять последовательностью и содержимым шагов на основе данных или действий пользователя. Асинхронные шаги критически важны при работе с элементами, которые появляются или изменяются после выполнения JavaScript-кода или запросов к серверу.

Настройка асинхронного шага

Каждый шаг в Driver.js обычно задаётся объектом с полями element, popover, description и т.д. Для асинхронных шагов добавляется свойство onNext, которое может быть асинхронной функцией:

const driver = new Driver();

driver.defineSteps([
  {
    element: '#button-load',
    popover: {
      title: 'Загрузка данных',
      description: 'Нажмите кнопку, чтобы инициировать асинхронный процесс.',
      position: 'bottom'
    },
    onNext: async () => {
      await loadData();
      console.log('Данные загружены, можно переходить к следующему шагу');
    }
  },
  {
    element: '#data-container',
    popover: {
      title: 'Контейнер данных',
      description: 'Здесь отображаются загруженные данные',
      position: 'top'
    }
  }
]);

driver.start();

В этом примере onNext использует асинхронную функцию loadData, которая может совершать AJAX-запросы или любые другие операции, возвращающие Promise. Driver.js ожидает завершения этого промиса, прежде чем перейти к следующему шагу.

Управление задержкой между шагами

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

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

driver.defineSteps([
  {
    element: '#button-process',
    popover: {
      title: 'Процессинг',
      description: 'Дождитесь завершения процесса'
    },
    onNext: async () => {
      await processData();
      await delay(1000); // задержка 1 секунда перед следующим шагом
    }
  }
]);

Использование такой задержки помогает синхронизировать визуальные элементы интерфейса с прогрессом асинхронных операций.

Асинхронное появление элементов

В веб-приложениях часто элементы интерфейса создаются динамически, и на момент инициализации шагов они могут отсутствовать в DOM. Driver.js позволяет обрабатывать такие сценарии с помощью async/await и проверки наличия элемента:

driver.defineSteps([
  {
    element: '#dynamic-element',
    popover: {
      title: 'Динамический элемент',
      description: 'Элемент появляется после асинхронной загрузки'
    },
    onNext: async () => {
      let el;
      while (!el) {
        el = document.querySelector('#dynamic-element');
        if (!el) await new Promise(r => setTimeout(r, 200));
      }
    }
  }
]);

Такой подход гарантирует, что Driver.js не попытается подсветить отсутствующий элемент, предотвращая ошибки и улучшая пользовательский опыт.

Асинхронные функции с возвратом значений

Иногда требуется, чтобы результат асинхронной операции влиял на содержимое следующих шагов. Для этого onNext может возвращать данные, которые затем можно использовать при конфигурации следующих шагов:

driver.defineSteps([
  {
    element: '#fetch-btn',
    popover: {
      title: 'Получение данных',
      description: 'Дождитесь ответа сервера'
    },
    onNext: async () => {
      const response = await fetch('/api/user');
      const data = await response.json();
      return data.username;
    }
  },
  {
    element: '#user-display',
    popover: {
      title: 'Пользователь',
      description: async () => {
        const username = await driver.steps[0].onNext();
        return `Привет, ${username}`;
      }
    }
  }
]);

Использование асинхронной функции в description позволяет динамически изменять текст поповера на основе полученных данных.

Комбинирование асинхронных и синхронных шагов

Driver.js не ограничивает последовательность шагов только асинхронными действиями. Можно комбинировать синхронные и асинхронные шаги для создания гибкого руководства:

driver.defineSteps([
  {
    element: '#start',
    popover: { title: 'Старт', description: 'Простой синхронный шаг' }
  },
  {
    element: '#load-data',
    popover: { title: 'Загрузка', description: 'Асинхронный шаг' },
    onNext: async () => await fetchData()
  },
  {
    element: '#finish',
    popover: { title: 'Финиш', description: 'Возврат к синхронному шагу' }
  }
]);

Такой подход позволяет создавать сложные интерактивные туры с ожиданием завершения операций, изменением DOM и обновлением интерфейса без нарушения логики последовательности.

Практические рекомендации

  • Всегда использовать await внутри onNext, чтобы Driver.js корректно ожидал завершения операции.
  • Проверять существование элементов динамически, если шаг зависит от асинхронной генерации контента.
  • Использовать асинхронные функции для изменения содержимого поповеров, чтобы интерфейс был актуален на момент отображения шага.
  • Комбинировать задержки и асинхронные вызовы для плавной и предсказуемой анимации подсветки элементов.

Асинхронные шаги в Driver.js обеспечивают полный контроль над динамическим интерфейсом, позволяя создавать туры, которые учитывают состояние приложения и данные, загружаемые в реальном времени.