Web3-интерфейс: загрузка, ошибка и пустой результат
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 9 из 30.
Чтение реестра не всегда заканчивается успешной карточкой. Узел может быть недоступен, ответ может задержаться, а запись курса может отсутствовать. В этом уроке введём явную модель состояния загрузки. Результат — посетитель понимает, что сейчас известно, что ещё ожидается и какой ответ нельзя считать отсутствием курса.
Предполагается функция readSnapshot и чистый модуль рендеринга. Вся работа остаётся локальной, сетевые примеры не запускались. Вместо фактически полученных результатов рассматриваем заданные последовательности ответов. Такое моделирование полезно ещё до появления кошелька: оно показывает ошибки взаимодействия обычного веб-интерфейса с асинхронным источником.
Четыре состояния чтения
idle означает, что запрос ещё не начинался. loading означает активное ожидание. ready содержит полученный снимок, который может описывать пустую запись. error содержит причину невозможности завершить чтение. Пустая запись не является пятым видом сетевой ошибки: это обычный ответ контракта, который уже классифицирует карточка.
Сохраняем lastGood, чтобы при кратком сбое не удалять полезное прежнее наблюдение. Но рядом показываем, что обновить его не удалось. Иначе последнее известное значение выдаётся за актуальное. Пользователь должен видеть разницу между «активный выпуск по прошлому снимку» и «сейчас подтверждено, что выпуск активен».
Номер запроса позволяет не принять поздний ответ старого чтения. Допустим, посетитель изменил конфигурацию и сразу обновил карточку. Второй запрос завершился быстро, первый — позже. Без сравнения поколения старый ответ перезапишет новый. Это гонка JavaScript-приложения, которая никак не связана с корректностью исполнения контракта.
Контроллер чтения
Ниже полный небольшой контроллер, принимающий функции чтения и отображения через параметры. Он не знает конкретного DOM и потому может использоваться и в карточке, и в будущем React-компоненте.
export function createReader(read, notify) {
let generation = 0;
let lastGood = null;
let disposed = false;
return {
async refresh() {
const current = ++generation;
notify({ status: 'loading', lastGood });
try {
const snapshot = await read();
if (disposed || current !== generation) return;
lastGood = snapshot;
notify({ status: 'ready', snapshot });
} catch (error) {
if (disposed || current !== generation) return;
notify({ status: 'error', lastGood, error });
}
},
invalidate() { generation++; },
dispose() { disposed = true; generation++; },
};
}
generation согласует результат с текущим интересом интерфейса. Он не останавливает уже отправленный HTTP-запрос: транспорт может продолжить работу. Польза в другом — завершение старого запроса больше не меняет страницу. Если транспорт поддерживает реальное прерывание, его можно добавить отдельно, сохранив правило поколения.
dispose относится к жизненному циклу страницы или компонента. После удаления карточки не нужно показывать её ошибку в уже другом экране. Это особенно важно в приложениях с внутренней навигацией: запрос переживает переход пользователя и возвращается в момент, когда прежний DOM уже не актуален.
Пользовательские сообщения
При loading можно показать «Читаем состояние реестра». Если есть lastGood, добавляем «Пока показано предыдущее наблюдение». При error предлагаем повторить чтение и раскрыть техническую причину в подробностях. Первая строка сообщения должна объяснять действие посетителю, а не перечислять внутренние классы библиотечной ошибки.
Нельзя показывать «Курс удалён», если RPC не ответил. Базовый реестр вообще не содержит метода удаления: деактивация оставляет отпечаток. Такое сообщение одновременно неверно описало бы транспортную проблему и семантику контракта. Корректная классификация начинается с простого вопроса: получили ли мы ответ состояния или только ошибку попытки чтения?
Для программной диагностики сохраняем тип сбоя, выбранную сеть, экземпляр и время. Приватные ключи в этом клиенте отсутствуют, но URL RPC может содержать служебные параметры. Полный адрес endpoint не следует бездумно включать в публичный текст ошибки или журнал аналитики. В диагностике достаточно безопасного имени конфигурации и категории проблемы.
Политика повтора
Повтор чтения после краткой сетевой ошибки обычно безопасен, потому что чтение не меняет storage. Однако бесконечные немедленные повторы нагружают endpoint и мешают восстановлению. Добавляют ограничение числа попыток, задержку и возможность ручного запроса. Для разных ошибок правила различаются: неправильная конфигурация не исправится тем же запросом через секунду.
Кнопку обновления можно временно отключить, пока активен запрос. Другой вариант — разрешить новый запрос и признать прежний устаревшим через поколение. Первый проще для маленькой страницы, второй полезен при смене фильтра или экземпляра. Выбор определяется сценарием, а не универсальным предпочтением одной техники.
При автоматическом обновлении фоновой вкладки следует ограничить частоту. Посетитель может открыть несколько вкладок каталога, и каждая создаст собственный поток чтения. Публичный RPC не знает, что запросы относятся к одному человеку; лимит быстро исчерпается. Позже введём кеш и координацию, но модель состояний остаётся прежней.
Пустой ответ и незнакомая схема
Успешный RPC не гарантирует, что результат подходит нашему интерфейсу. ABI может быть устаревшим или экземпляр — не тем. На границе чтения проверяются ожидаемые типы и привязка к конфигурации. Ошибка декодирования не отображается как «в реестре нет курса»: это проблема интерпретации или выбора экземпляра.
Если getter вернул нулевой отпечаток и false, карточка честно показывает начальную запись. Пользователь может обновить её после будущей публикации. Состояние ready означает завершённое чтение, а не обязательное существование активного курса. Такое разделение позволяет сохранить небольшой универсальный контроллер и вынести смысл записи в отдельный модуль.
Для упражнения задайте на бумаге порядок завершения запросов 1 и 2, где второй начат позже и закончен раньше. Затем добавьте удаление карточки между запросом и ответом. Ожидается, что ни поздний ответ, ни ошибка удалённой карточки не изменят новый экран. Так вы получите ясный критерий согласования асинхронной работы до добавления подписей. Модель Promise описана в документации JavaScript.
Несколько источников загрузки
Карточка может читать реестр и отдельно загружать манифест. Эти ожидания имеют разные статусы. Если реестр ответил, а файл недоступен, публикация остаётся наблюдаемой, но проверка байтов не завершена. Один общий loading на весь экран часто не позволяет объяснить эту ситуацию. Лучше сохранить две небольшие модели и соединить их в представлении.
Проверка отпечатка также может завершиться после появления нового снимка. Результат сравнения относится к прежнему ожидаемому хешу и не должен автоматически помечать новый выпуск проверенным. Контроллер манифеста сохраняет отпечаток, для которого начат запрос, и сравнивает его с актуальной карточкой. Здесь снова полезен знакомый номер поколения: асинхронный ответ имеет конкретное основание.