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

Ваше приложение Javascript запускает любое количество асинхронных задач, реагируя на их результат. Вы старательно управляете жизненным циклом своих задач, но спрашиваете себя, неужели это еще один шаблон? Вы учли все крайние случаи или на этот раз что-то упустили?

Оболочка задач, которая может реагировать, отменять, ограничивать время и перезапускать. Нажмите здесь, чтобы посмотреть живую демонстрацию.

В этой статье мы рассмотрим JavaScript Task Wapper для произвольной работы, на которую можно реагировать, которая может быть отменена, ограничена по времени и перезапущена.

Мы заключим его в миксин Vue и применим в одностраничном приложении Vue, которое извлекает изображения из Flickr и реагирует на все его состояния жизненного цикла.

Обертка задачи

Обертка задачи - это не ракетостроение. Вот как выглядит типичный жизненный цикл оболочки:

  1. При создании задача неактивна. Вызов start(request) запусков инкапсулированной работы, и задача выполняется.
  2. Когда работа завершается успешно, он вызывает done(response), чтобы уведомить о задаче. Задача выполнена.
  3. Если работа не удается, он вызывает error(reason), чтобы уведомить о задаче. Задача не выполнена.
  4. Работающую задачу можно в любой момент отменить, позвонив cancel(reason). Задача отменена.
  5. При желании задача может запускать таймер для отслеживания времени выполнения запущенной задачи и останавливать задачу, если таймер срабатывает. У задачи тайм-аут.
  6. Задачу можно сбросить в любое время, при этом данные задачи будут очищены и отправлены обратно в бездействие. Если он запущен, задача сначала отменяется. Теперь задачу можно перезапустить (шаг 1).

В сторонуⁿ: Под капотом наш класс Task - это, прежде всего, Детерминированный конечный автомат (DFA). Мы собираемся дать ему память (магнитофон для компьютерных фанатов), так что мы действительно машина Тьюринга. В отличие от ANSI C / C ++, язык JavaScript не накладывает ограничений на доступную память (в отличие от физических ограничений и реалий интерпретатора). Это делает нас завершенными по Тьюрингу!

Более глубокое погружение

Использование DFA в классе Task дает нам основную часть наших требований - переход между состояниями (или пребывание в текущем состоянии) на основе пользовательского ввода и уведомлений от инкапсулированной работы.

Нам нужно будет дополнить DFA кодом, например для инициализации данных в состоянии ожидания и запуска таймера при работе и предоставления ему памяти для управления запросом и результатом , инкапсулированная работа.

Поскольку мы разрешаем перезапуск Задачи (после сброса), нам нужно будет принять меры против устаревших уведомлений о завершении или ошибках из-за ранее отмененной, но незавершенной работы. Мы создадим уникальный идентификатор сеанса при переходе в состояние простоя и потребуем, чтобы в уведомления о работе был включен идентификатор сеанса.

Примечание разработчика. Мы намеренно пропускаем танец cancel / cancelDone, потому что наши сценарии использования не могут дождаться завершения отмены, например при сбросе во время работы мы хотим немедленно начать следующий сеанс, когда пользователь отменяет текущий поиск и начинает новый.

На рисунке 2 показана диаграмма состояний DFA, дополненная блоками кода и решений.

Класс задачи - реализация

Выбор конечного автомата

Вот что мы хотим от нашего конечного автомата (помимо простого DFA):

  • Обычные переходы между состояниями DFA CurrentState x Input/Event --> NextState x Actions.
  • Сохраняйте данные и разрешайте синхронизацию мутаций с переходами между состояниями - нам нужна гарантия того, что данные не изменятся, пока мы их проверяем.
  • Укажите entry события, чтобы мы могли выполнять инициализацию, уведомления в нужное время и только один раз.
  • Запускаемые таймеры, которые генерируют timeout событий, но также отменяют, когда они больше не нужны, без исчерпывающего перечисления условий отмены. В частности, нам нужен таймер, который запускается, когда конечный автомат переходит в работу, но немедленно отключается, если конечный автомат переходит в другое состояние до срабатывания таймера. Таймер не должен перезапускаться, если конечный автомат переходит обратно в (т.е. остается в) работающим (например, если получен устаревший done).
  • Выборочно получать или postpone событий - например, когда мы получаем запрос сброса во время работы. Мы хотим перейти к отмене перед бездействием, чтобы уведомить работу о выполнении любой необходимой очистки. Отсрочка сброса и первый переход к отмене позволяет конечному автомату выполнить сброс, когда состояние cancel. Альтернативой будет добавление памяти для отслеживания необходимости перехода от выполнения → отмены → сброса.

Мы будем использовать gen-statem фреймворк конечного автомата, который отвечает всем нашим требованиям (бесстыдное предупреждение о подключении).

Конечный автомат - обработчики событий

Чтобы запрограммировать наш конечный автомат, нам нужно предоставить gen-statem упорядоченный список обработчиков событий:

  • Когда мы вводим idle, генерируем init и сбрасываем данные конечного автомата.
  • Когда мы получим start, запишите запрос и перейдите к running.
  • Когда мы вводим running, генерируем run с данными конечного автомата (включая идентификатор сеанса), чтобы начать работу. Если был запрошен тайм-аут, скажите конечному автомату запустить stateTimeout. Это сработает, если конечный автомат не перейдет в другое состояние в течение периода времени.
  • Когда work уведомит нас о завершении, убедитесь, что идентификатор сеанса актуален, прежде чем записывать какой-либо результат, а затем перейдите к done/done. В противном случае просто оставайся в
  • Если мы running и получаем reset событие, отложите мероприятие и сначала перейдите к done/cancel. Это гарантирует, что работа получит cancel уведомление (для выполнения очистки). Отсрочка reset гарантирует, что она будет повторена в done/cancel, что приведет к переходу конечного автомата на idle.
  • Если мы получим cancel, когда running, запишите любую причину отмены и перейдите к done/cancel.
  • Если мы получаем error, когда running, запишите любую указанную причину сбоя и перейдите к done/error, сначала убедившись, что сеанс является текущим.
  • done/* состояния - это состояния завершения. Когда мы enter любой из них, отправляем уведомление типа (с данными).
  • Наконец, если мы получим stateTimeout, когда running, перейдите к done/timeout.

Класс задачи - открытый интерфейс

  • start(request) Начинает инкапсулированную работу, предварительно предоставив ей любую информацию запроса. Пользователь должен прослушивать событие run, которое теперь запускается с данными конечного автомата (включая запрос и идентификатор текущего сеанса), и инициировать работу.
  • done(sessionId, result) Призван сигнализировать об окончании работы. Идентификатор текущего сеанса должен быть предоставлен вместе с любым результатом работы. Результат записывается в данные конечного автомата.
  • error(reason) Сообщает задаче, что работа завершилась с ошибкой. Может быть указана необязательная причина сбоя, которая записывается в данных конечного автомата. Нет эффекта, если задача не running.
  • cancel(reason) Вызывается для отмены выполняющейся задачи вместе с необязательной причиной отмены, которая записывается в данных конечного автомата. Нет эффекта, если задача не running.
  • reset() Указывает задаче вернуться к idle. Если задача running, она сначала отменяется.
  • exec(fn: () => Promise) Удобный метод, который запускает задачу и управляет правильным сообщением идентификатора сеанса. Должен вызываться с функцией, которая принимает нулевые параметры и возвращает Promise. exec вызывает данную функцию и вызывает done(), если возвращенное обещание разрешается (или error(), если возвращенное обещание отклоняется).

Vue Task Mixin

Мы добавим наш класс Task в Vue в качестве миксина для повторного использования.

Характеристики

  • taskTimeout Необязательный тайм-аут в миллисекундах.

Данные

  • task.sm Экземпляр конечного автомата
  • task.state Текущее состояние конечного автомата. Используется для вычисляемых свойств.
  • task.error Любая ошибка, указанная error() или cancel().
  • task.result Результат предоставлен done().

Вычисленные свойства

  • taskRunning Верно, если задача - running.
  • taskSucceeded Верно, если работа завершилась успешно.
  • taskFailed Верно, если работа завершилась ошибкой (называется error())
  • taskCancelled Истина, если задача была отменена.
  • taskTimedOut Истина, если истекло время ожидания задачи.

Методы

VueTaskMixin предоставляет следующие методы:

  • startTask(...args) Запускает задачу с заданными аргументами.
  • cancelTask(reason) Отменяет задание по указанной причине.
  • resetTask() Сбрасывает задачу
  • startTaskWithReset(...args) Запускает задачу, но сначала выполняет сброс, отменяющий любую текущую задачу.
  • async runTask(...args) Это обратный вызов, вызываемый для фактического выполнения инкапсулированной работы . Должен быть переопределен в компоненте Vue.

Обратный вызов runTask должен быть переопределен в компоненте Vue, используя миксин для фактического выполнения инкапсулированной работы.

Приложение Vue Flickr

Мы опубликовали одностраничное приложение, чтобы продемонстрировать использование VueTaskMixin в реальных условиях.

Нажмите здесь для онлайн-демонстрации.

Приложение перебирает следующие сценарии:

  • Ввод текста в поле input и нажатие Search извлекает (через start()) недавние изображения с Flickr, помеченные текстом.
  • Время ожидания истекло до 2000ms
  • Миниатюрные изображения, полученные с Flickr, отображаются под полем input (через done()).
  • Запрос задерживается на одну секунду, чтобы задача оставалась в running. Пока задача running, свойство миксина taskRunning меняет местами кнопку Search на кнопку Cancel, позволяя пользователю имитировать cancel() операцию.
  • При повторной отправке запроса в любое время выдается reset(), за которым следует start().
  • Каждый четвертый поиск занимает больше, чем 2000ms, чтобы имитировать тайм-аут.
  • Каждый пятый поиск выдает ошибку для имитации error().

Вывод

В этой статье мы рассмотрели дизайн конечного автомата с необходимыми функциями для обработки типичного сценария выборки данных, который можно отменить и перезапустить, с необязательным тайм-аутом; миксин Vue, который предоставляет это как многоразовую функциональность и демонстрацию с простым приложением для выборки данных.

Мы надеемся, что эта функция или ее вариант освободят шаблонный код в вашем следующем приложении.