edit full article archive to publication standard
Build and deploy / deploy (push) Successful in 18s

This commit is contained in:
2026-07-31 23:08:19 +03:00
parent 440c8721dc
commit 7c5b19c960
28 changed files with 583 additions and 39 deletions
+2 -2
View File
@@ -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> и не изменит поле.'),