Асинхронные задачи¶
Обзор¶
Иногда компоненту требуется отобразить данные, которые доступны только асинхронно. Такие данные могут быть получены с сервера, из базы данных или вообще получены или вычислены из асинхронного API.
В то время как жизненный цикл реактивных обновлений Lit является пакетным и асинхронным, шаблоны Lit всегда отображаются синхронно. Данные, используемые в шаблоне, должны быть доступны для чтения в момент рендеринга. Для асинхронного рендеринга данных в компоненте Lit необходимо дождаться готовности данных, сохранить их так, чтобы они были доступны для чтения, а затем запустить новый рендеринг, который может использовать данные синхронно. Часто приходится решать, что рендерить, пока данные находятся в процессе выборки, или когда выборка данных также не удалась.
Пакет @lit/task
предоставляет реактивный контроллер Task
, который помогает управлять этим асинхронным рабочим процессом с данными.
Task
— это контроллер, который принимает функцию асинхронной задачи и запускает ее либо вручную, либо автоматически при изменении ее аргументов. Task хранит результат выполнения функции задачи и обновляет элемент хоста, когда функция задачи завершается, чтобы результат можно было использовать при рендеринге.
Пример¶
Это пример использования Task
для вызова HTTP API через fetch()
. API вызывается каждый раз, когда изменяется параметр productId
, а компонент выводит сообщение о загрузке при получении данных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Особенности¶
Task заботится о ряде вещей, необходимых для правильного управления асинхронной работой:
- Собирает аргументы задачи при обновлении хоста
- Запускает функции задачи при изменении аргументов
- Отслеживает статус задачи (начальный, ожидающий, завершенный или ошибка)
- Сохраняет последнее значение завершения или ошибки функции задачи
- Запускает обновление хоста при изменении статуса задачи
- Обрабатывает условия гонки, гарантируя, что только последний вызов задачи завершит ее выполнение
- Выдает правильный шаблон для текущего состояния задачи
- Позволяет прерывать задачи с помощью
AbortController
.
Это убирает большую часть шаблонов для правильного использования асинхронных данных из вашего кода и обеспечивает надежную обработку условий гонки и других нештатных ситуаций.
Что такое асинхронные данные?¶
Асинхронные данные — это данные, которые не доступны сразу, но могут быть доступны в будущем. Например, вместо значения типа строки или объекта, которое можно использовать синхронно, обещание предоставляет значение в будущем.
Асинхронные данные обычно возвращаются из асинхронного API, который может быть представлен в нескольких формах:
- Обещания или асинхронные функции, например
fetch()
. - Функции, принимающие обратные вызовы
- Объекты, которые испускают события, такие как события DOM
- Библиотеки, такие как observables и signals.
Контроллер Task работает с обещаниями, поэтому независимо от формы вашего async API вы можете адаптировать его к обещаниям для использования с Task.
Что такое задача?¶
В основе контроллера Task лежит само понятие "задача".
Задача — это асинхронная операция, которая выполняет некоторую работу по созданию данных и возвращает их в виде Promise. Задача может находиться в нескольких различных состояниях (начальное, ожидание, завершение и ошибка) и может принимать параметры.
Задача — это общая концепция, которая может представлять любую асинхронную операцию. Они лучше всего подходят, когда есть структура "запрос/ответ", например, получение данных из сети, запрос к базе данных или ожидание одного события в ответ на какое-то действие. Они менее применимы к спонтанным или потоковым операциям, таким как бесконечный поток событий, потоковый ответ базы данных и т. д.
Установка¶
1 |
|
Использование¶
Task
— это реактивный контроллер, поэтому он может реагировать на обновления и запускать их в рамках жизненного цикла реактивного обновления Lit.
Как правило, у вас будет один объект Task для каждой логической задачи, которую должен выполнять ваш компонент. Установите задачи в качестве полей вашего класса:
1 2 3 4 5 |
|
1 2 3 4 5 |
|
Как поле класса, статус и значение задачи легко доступны:
1 2 |
|
Функция задачи¶
Наиболее важной частью объявления задачи является функция задачи. Это функция, которая выполняет фактическую работу.
Функция задачи задается в опции task
. Контроллер задач будет автоматически вызывать функцию задачи с аргументами, которые передаются отдельным обратным вызовом args
. Аргументы проверяются на изменения, и функция задачи вызывается только в том случае, если аргументы изменились.
Функция задачи принимает аргументы задачи в виде массива, передаваемого в качестве первого параметра, и аргумент options в качестве второго параметра:
1 2 3 4 5 6 |
|
Массив args
функции задачи и обратный вызов args
должны быть одинаковой длины.
Напишите функции task
и args
как стрелочные функции, чтобы ссылка this
указывала на элемент-хост.
Состояние задачи¶
Задачи могут находиться в одном из четырех состояний:
INITIAL
: Задача не была запущенаPENDING
: Задача запущена и ожидает нового значенияCOMPLETE
: Задача успешно завершенаERROR
: Задача завершилась с ошибкой
Статус задачи доступен в поле status
контроллера Task, и представлен перечислительным объектом TaskStatus
, который имеет свойства INITIAL
, PENDING
, COMPLETE
и ERROR
.
1 2 3 4 5 6 |
|
Обычно задача переходит от INITIAL
к PENDING
, затем к одному из COMPLETE
или ERROR
, а затем возвращается к PENDING
, если задача выполняется повторно. Когда задача меняет статус, она запускает обновление хоста, чтобы хост-элемент мог обработать новый статус задачи и выполнить рендеринг, если это необходимо.
Важно понимать, в каком статусе может находиться задача, но обычно нет необходимости обращаться к нему напрямую.
В контроллере Task есть несколько элементов, которые относятся к состоянию задачи:
status
: статус задачи.value
: текущее значение задачи, если она завершилась.error
: текущая ошибка задачи, если она ошиблась.render()
: метод, который выбирает обратный вызов для выполнения, основываясь на текущем статусе.
Задачи рендеринга¶
Самым простым и распространенным API для рендеринга задачи является task.render()
, поскольку он сам выбирает нужный код для запуска и предоставляет ему соответствующие данные.
render()
принимает объект config с необязательным обратным вызовом для каждого статуса задачи:
initial()
pending()
complete(value)
error(err)
.
Вы можете использовать task.render()
внутри метода Lit render()
для рендеринга шаблонов в зависимости от статуса задачи:
1 2 3 4 5 6 7 8 9 10 |
|
Запуск задач¶
По умолчанию задачи запускаются при любом изменении аргументов. Это контролируется опцией autoRun
, которая по умолчанию имеет значение true
.
Автозапуск¶
В режиме автозапуска задача будет вызывать функцию args
при обновлении хоста, сравнивать аргументы с предыдущими аргументами и вызывать функцию задачи, если они изменились. Задача, в которой не определена функция args
, находится в ручном режиме.
Ручной режим¶
Если для autoRun
установлено значение false, задача будет находиться в ручном режиме. В ручном режиме вы можете запустить задачу, вызвав метод .run()
, возможно, из обработчика событий:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
В ручном режиме вы можете предоставлять новые аргументы непосредственно в run()
:
1 |
|
Если аргументы не переданы в run()
, они собираются из обратного вызова args
.
Прерывание задач¶
Функция задачи может быть вызвана в то время, когда предыдущие задачи еще не выполнены. В этом случае результаты выполнения задач будут проигнорированы, и вам следует попытаться отменить все невыполненные работы или сетевые операции ввода-вывода, чтобы сэкономить ресурсы.
Это можно сделать с помощью сигнала AbortSignal
, который передается в свойстве signal
второго аргумента функции task. Когда ожидающий запуск задачи заменяется новым запуском, сигнал AbortSignal
, переданный ожидающему запуску, прерывается, чтобы дать сигнал запуску задачи отменить все ожидающие работы.
AbortSignal
не отменяет никакой работы автоматически — это просто сигнал. Чтобы отменить какую-то работу, вы должны либо сделать это самостоятельно, проверив сигнал, либо передать сигнал другому API, принимающему AbortSignal
, например fetch()
или addEventListener()
.
Самый простой способ использовать AbortSignal
— переслать его в API, который его принимает, например fetch()
.
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
Передача сигнала в fetch()
приведет к тому, что браузер отменит запрос к сети, если сигнал будет прерван.
Вы также можете проверить, был ли сигнал прерван, в вашей функции задачи. Проверять сигнал следует после возврата в функцию задачи из асинхронного вызова. Удобным способом для этого является throwIfAborted()
:
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 |
|
Цепочка задач¶
Иногда требуется запустить одну задачу после завершения другой. Это может быть полезно, если задачи имеют разные аргументы, чтобы связанная задача могла выполняться без повторного запуска первой задачи. В этом случае первая задача будет использоваться как кэш. Для этого вы можете использовать значение задачи в качестве аргумента другой задачи:
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
Вы также можете часто использовать одну функцию задачи и ожидать промежуточных результатов:
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 |
|