edit full article archive to publication standard
Build and deploy / deploy (push) Successful in 18s
Build and deploy / deploy (push) Successful in 18s
This commit is contained in:
@@ -194,7 +194,7 @@ const practiceArticle = createRevision(
|
||||
readingMinutes: 12,
|
||||
},
|
||||
[
|
||||
paragraph('Симптом знакомый: почта в форме стала зелёной, пользователь нажал «Сохранить», а сервер вернул ошибку формата или занятости. Ещё хуже, когда ответ приходит, но текст попадает в общий баннер, а не к полю. Человек исправляет значение наугад, повторяет запрос и может создать дубль. Причина обычно не в одном регулярном выражении: у клиента, сервера и представления ошибки разные правила и разные владельцы состояния.'),
|
||||
paragraph('Симптом знакомый: почта в форме стала зелёной, пользователь нажал «Сохранить», а сервер вернул ошибку формата или занятости. Ещё хуже, когда ответ приходит, но текст попадает в общий баннер, а не к полю. Человек исправляет значение наугад, повторяет запрос и может создать дубль. Причина обычно не в одном регулярном выражении: у клиента, сервера и представления ошибки разные правила и разные владельцы состояния. Цена ошибки — лишняя отправка формы и неверное решение пользователя.'),
|
||||
paragraph('В апреле 2019 я бы не пытался строить «универсальный валидатор». Для одной формы достаточно зафиксировать короткий контракт: какие ограничения браузер проверяет сразу, какие условия знает только сервер, в каком виде сервер возвращает ошибки и кто имеет право менять состояние поля. Ниже учебный вариант без привязки к фреймворку. Он показывает маршрут проверки; он не является результатом запуска на чужом API или браузерной трассой.'),
|
||||
heading('Сначала разделяем три вида проверки'),
|
||||
paragraph('Клиентская проверка нужна, чтобы не отправлять пустую почту или строку с очевидно неверной формой. HTML уже знает часть ограничений: <code>required</code>, <code>type="email"</code>, <code>minlength</code>, <code>pattern</code>. У контрола есть <code>validity</code>, а <code>checkValidity()</code> отвечает на конкретный вопрос: проходит ли элемент его ограничения. Это удобный ранний фильтр, но не источник истины о пользователе, правах, занятости логина или правилах, которые меняются на сервере.'),
|
||||
@@ -493,7 +493,7 @@ const fieldArticle = createRevision(
|
||||
readingMinutes: 12,
|
||||
},
|
||||
[
|
||||
paragraph('Разбор начинается с симптома, а не с библиотеки. Пользователь вводит логин <code>ivan</code>; форма отправляет проверку. Через мгновение он меняет значение на <code>ivanka</code>. Новый ответ говорит «свободно», экран становится зелёным. Затем приходит старый ответ «занято» и рисует красную строку уже под <code>ivanka</code>. Пользователь видит противоречие, а поддержка получает скриншот, по которому невозможно понять, какое значение проверял сервер.'),
|
||||
paragraph('Разбор начинается с симптома, а не с библиотеки. Пользователь вводит логин <code>ivan</code>; форма отправляет проверку. Через мгновение он меняет значение на <code>ivanka</code>. Новый ответ говорит «свободно», экран становится зелёным. Затем приходит старый ответ «занято» и рисует красную строку уже под <code>ivanka</code>. Пользователь видит противоречие, а поддержка получает скриншот, по которому невозможно понять, какое значение проверял сервер. Цена ошибки — заблокировать корректный ввод или отправить устаревший результат.'),
|
||||
paragraph('Причина — гонка двух корректных по отдельности promise. Код записывает любой завершившийся ответ в одно состояние поля и не хранит, к какому вводу он относится. Вторая проблема обычно рядом: строка ошибки лежит в общем баннере, поэтому даже настоящий серверный отказ нельзя быстро привязать к input. Ниже учебный fixture и маршрут расследования. Он не описывает production-трассу, не заявляет о запуске браузера и не заменяет проверку конкретного API.'),
|
||||
heading('Реконструкция гонки без настоящей сети'),
|
||||
paragraph('Для расследования нам не нужен медленный сервер. Достаточно детерминированно задать два ответа в обратном порядке. Функция <code>delayResult</code> в автономном пакете считает <code>ivan</code> занятым и возвращает его спустя 30 мс; <code>ivanka</code> свободен и возвращается спустя 5 мс. Две проверки стартуют одна за другой. Если код применяет всё подряд, первый результат перезапишет второй. Если он сравнивает идентификатор, первый результат станет <code>stale-response</code> и не изменит поле.'),
|
||||
|
||||
Reference in New Issue
Block a user