Svelte

Асинхронные данные и реактивность

Виджет погоды на дашборде грузит данные из API при выборе города. Пока запрос летит, нужен спиннер. Если запрос упал, нужно сообщение об ошибке и кнопка повтора. Когда данные пришли, из них считается ещё и совет: брать ли зонт. Тянуть для этого тяжёлую библиотеку загрузки данных избыточно, когда виджет один. Руны Svelte 5 покрывают этот сценарий тремя реактивными значениями и одним эффектом, без внешних зависимостей.

  • Автодополнение поиска: каждый ввод запускает запрос, прошлые результаты сбрасываются, показывается состояние загрузки
  • Виджеты дашборда: каждый блок грузит свои данные и сам управляет своими состояниями загрузки и ошибки
  • Подгрузка деталей по выбору: выбор элемента в списке тянет его детали с показом спиннера в панели
  • Формы с проверкой на сервере: асинхронная валидация поля при вводе с индикатором и сообщением об ошибке

Предварительные знания

  • Понимание производных значений через руну `$derived`
  • Знание эффектов через руну `$effect` и того, когда они перезапускаются
  • Базовое знание fetch и промисов: запрос, ожидание ответа, обработка ошибки

Складываем асинхронные данные в реактивное состояние

Промис сам по себе не реактивен: его результат приходит позже, и интерфейс должен на это отреагировать. Приём состоит в том, чтобы завести реактивное состояние через `$state` и записать в него результат, когда промис разрешится. Запрос запускают в `$effect`, который повторно выполняется при изменении своих зависимостей, например выбранного идентификатора. Так смена входа автоматически перезагружает данные.

Эффект читает city, поэтому city становится его зависимостью. При смене города эффект перезапускается и тянет новые данные. Когда промис разрешается, запись в weather обновляет реактивное состояние, и разметка перерисовывается. Промежуточное значение null до прихода данных и даёт простейший индикатор загрузки в этом примере.

Асинхронный коллбэк внутри `$effect` нельзя превращать в async-функцию самого эффекта: функция очистки эффекта должна возвращаться синхронно. Запрос запускают внутри тела эффекта, а результат записывают в состояние в then или после await во вложенной функции, не делая сам колбэк эффекта асинхронным.

В `$effect` читается city и запускается fetch по этому городу. Что произойдёт, когда пользователь сменит город?

Паттерн loading и error на трёх полях

Полноценный асинхронный виджет различает три состояния: идёт загрузка, пришла ошибка, пришли данные. Их моделируют тремя реактивными полями: data, loading и error. Перед запросом ставят loading в true и сбрасывают error. В случае успеха пишут data и снимают loading. В случае сбоя пишут error и тоже снимают loading. Разметка выбирает, что показать, по этим трём полям.

Порядок веток важен: сначала проверяют loading, затем error, затем данные. Сброс error перед каждым запросом убирает сообщение о прошлой неудаче при повторной попытке. Блок finally гарантирует, что loading снимется и при успехе, и при ошибке. Это полноценная модель состояний без какой-либо внешней библиотеки, на чистых рунах.

ПолеТипРоль
dataT | nullПолученные данные или null до их прихода
loadingbooleanИдёт ли сейчас запрос
errorstring | nullСообщение об ошибке или null, если ошибки нет

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

Зачем перед каждым новым запросом сбрасывать error в null, прежде чем ставить loading в true?

Производные от данных и гонка устаревших ответов

Когда данные лежат в реактивном состоянии, производные от них считаются обычным `$derived`. Совет брать зонт это функция от полученной погоды, и он пересчитывается сам, как только weather обновится. Производное не нужно вручную синхронизировать с загрузкой: оно зависит от data, и приход данных автоматически запускает его пересчёт.

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

При смене city эффект сначала вызывает функцию очистки прошлого запуска, выставляя cancelled в true для старого запроса, и только потом запускается заново. Когда поздний ответ старого запроса всё же придёт, он увидит cancelled и не запишет устаревшие данные. Так последним в weather всегда оказывается ответ на актуальный запрос, а не тот, что просто вернулся последним по времени.

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

Пользователь быстро переключил город с Москвы на Берлин. Запрос по Москве из-за задержки сети вернулся позже запроса по Берлину. Как флаг cancelled в функции очистки эффекта предотвращает показ устаревших данных?

Связь с другими темами

Этот урок про асинхронные данные на клиенте. Он перекликается со стримингом и сторами:

  • `$derived` — Совет из погоды это производное от полученных данных через `$derived`
  • Стриминг из load — Тот же приём каркаса и поздних данных, но на сервере через возврат промиса из load
  • Stores и интероп — Сторы дают альтернативный способ обернуть асинхронный поток данных

Итог

  • Асинхронные данные складывают в реактивное состояние через `$state`: отдельные поля под данные, состояние загрузки и ошибку
  • Запрос запускают в `$effect`, который перезапускается при изменении входа (например, выбранного города), и грузит свежие данные
  • Производные значения от полученных данных считают через `$derived`, и они пересчитываются, когда данные приходят
  • Паттерн loading и error строится на трёх полях состояния и не требует тяжёлой библиотеки для простых случаев
  • Гонку устаревших ответов гасят проверкой актуальности запроса, чтобы поздний ответ не перезаписал свежий

Связанные уроки

  • sv-07-derived — Производные от полученных данных строятся на `$derived`, поэтому понимание производных значений обязательно
  • sv-29-streaming — Стриминг из load и клиентская загрузка решают одну задачу раннего каркаса и поздних данных, но в разных средах
  • sv-32-stores-interop — Сторы исторически удобны для асинхронных потоков, что даёт альтернативный взгляд на ту же задачу
Асинхронные данные и реактивность

0

1

Войти