Покажите, что сервер действительно принял

JavaScriptBeginner
Практиковаться сейчас

Введение

Подписи, условная заметка и адаптивный макет Still & Spoke готовы. Текущий обработчик может показать демонстрационное подтверждение без отправки записи. Вы замените его настоящим асинхронным запросом и сообщениями о фактическом принятии студией. Правильный вид страницы ещё не запись, пока готовый сервис не вернул подтверждение.

Нужны HTML-формы, основы CSS-макета, объекты, функции, выбор DOM и события JavaScript. Изучаются JSON, async/await, fetch, результаты HTTP, состояние ожидания и сообщения с возможностью восстановления. Семантика и условные ограничения готовы; предыдущая работа полезна, но её VM-файлы не нужны. Меняйте только /home/labex/project/stillspoke/request.js и feedback.js. Сохраните остальные файлы, API, вместимость и посетителей. Личности вымышлены; почта и платежи не отправляются. 60 минут — оценка автора, не измерение работы новичков.

Используйте Workspace для полной замены файлов, Studio для сайта. Study form — независимый рабочий пример, его записи не завершают вашу страницу. Studio records читает принятые записи. Practice room — отдельная тренировочная страница, отклоняющая один запрос до любого принятия. Каждая работа имеет независимые данные. Готовый backend, промышленное развёртывание, идемпотентность произвольных запросов и полное соответствие доступности не изучаются.

Отправьте один настоящий запрос и дождитесь ответа

Замените демонстрацию реальным POST, запретите повторную активацию во время ожидания и покажите подлинное подтверждение сервера.

В Studio выберите Study form. Через Tab дойдите до Your name, введите Example Guest; Tab к Email, введите sample@example.test; Tab к Session, нажмите C, прочитайте Clay, slowly / 10:00. Пройдите Tab мимо снятого флажка и необязательной заметки к Reserve my place. Нажмите Enter один раз. Дождитесь настоящей Confirmation SS-…, откройте Studio records и сверьте имя, почту, время. Вернитесь через Back to signup и Work form. У примера другая база. На вашей странице по-прежнему только исходные посетители; демонстрационный текст не устанавливает принятие.

Пример показывает ожидаемый вид: отдельный заголовок подтверждения и читаемое время. Ваш номер отличается.

Независимый пример после настоящей записи

Готовый API — часть локального окружения. Его публичные поля и правила:

Поле JSON Значение
name Не менее двух символов после удаления внешних пробелов.
email Вымышленный адрес с окончанием @example.test. Браузер сначала проверяет синтаксис.
session Стабильный ID из списка: clay-morning для 10:00 или clay-afternoon для 14:00. Дневное время уже занято.
support Булево значение выбора помощи с местом.
note Строка минимум из пяти символов при помощи, иначе необязательна.

POST посылает предложение новой записи в api/bookings. Это относительный URL под текущей рабочей или примерной страницей, поэтому их бизнес-пространства разделены. API возвращает JSON — текстовое представление объектов, строк, чисел и булевых значений. Имена свойств и строки JSON заключаются в двойные кавычки. JSON.stringify(data) превращает объект JavaScript в JSON-текст; response.json() разбирает ответ обратно в объект. Заголовок Content-Type: application/json сообщает содержимое тела. Текст frontend и отключённая кнопка не реализуют правило вместимости сервиса.

Статусы HTTP различают исходы:

Статус Ответ и установленный факт
201 booking содержит принятые поля, понятный sessionTitle и созданный сервером id. Запись принята, номер начинается с SS-. Никогда не выдумывайте его.
422 errors сопоставляет полям объяснения. Ничего не записано; исправьте отмеченное.
409 Сеанс заполнен. Ничего не записано; выберите свободный.
503 Управляемая пауза отказала до принятия. Ничего не записано; безопасно повторить.

fetch возвращает Promise — будущий результат. Обработчик async использует await, чтобы приостановить собственную работу до успеха или неудачи Promise, не лишая браузер отзывчивости. Сначала ждите HTTP-ответ, затем разбор JSON. Отказ HTTP, например 422 или 503, тоже успешно завершает fetch; он не переводит автоматически в catch. Явно проверяйте статус. Ошибка сети или разбора может попасть в catch, не определяя, была ли запись принята. Сообщение должно говорить о неизвестном принятии и пути восстановления через Studio records.

submit охватывает кнопку и Enter в поле. event.preventDefault() отменяет стандартную отправку с переходом, позволяя отправлять JSON. Поля, не прошедшие встроенную проверку, останавливают отправку до обработчика. form.elements ищет по name, не видимой позиции или HTML-id. Текстовые поля читаются через .value, флажок через .checked.

Установите pending перед любым ожиданием. Защита немедленно возвращается при новом событии во время ожидания, а .disabled заметно отключает стандартную кнопку. Это предотвращает повторную активацию на этой странице для текущего запроса, не гарантируя защиту от других страниц и произвольных параллельных клиентов. try выполняет запрос, catch показывает неизвестное принятие при сбое, finally восстанавливает кнопку и очищает пассивное сообщение при любом исходе. Сохраняйте значения, не сбрасывая форму.

clearErrors, showMessage, showResponse — функции из feedback.js. После разбора HTML страница загружает через defer, по порядку, scenario.js, validity.js, feedback.js, request.js. Первый даёт данные, второй — условную нативную логику, последние два совместно обрабатывают запрос и сообщения. Текущий файл обслуживает нормальный 201; отказы полей вы улучшите далее.

В Workspace выберите каталог через cd. cat > request.js заменяет только этот файл. Маркер here-document JS в кавычках оставляет текст буквальным до отдельной строки JS; вставьте всё без обратных кавычек Markdown. Возврат приглашения означает сохранение.

cd /home/labex/project/stillspoke
cat > request.js <<'JS'
const form = document.querySelector('form');
const reserveButton = form.querySelector('[type="submit"]');
const progress = document.querySelector('#progress');
let pending = false;
form.addEventListener('submit', async (event) => {
  event.preventDefault();
  if (pending) return;
  clearErrors();
  document.querySelector('#result').hidden = true;
  const data = {
    name: form.elements.name.value,
    email: form.elements.email.value,
    session: form.elements.session.value,
    support: form.elements.support.checked,
    note: form.elements.note.value
  };
  pending = true;
  reserveButton.disabled = true;
  progress.textContent = 'Sending your request...';
  try {
    const response = await fetch('api/bookings', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    const body = await response.json();
    showResponse(response.status, body);
  } catch (error) {
    showMessage('No confirmation received. Acceptance is unknown. Check Studio records; wait for the original attempt to be resolved before a new submission.');
  } finally {
    pending = false;
    reserveButton.disabled = false;
    progress.textContent = '';
  }
});
JS

Вернитесь в Studio, обновите, введите Morgan Vale, morgan@example.test, выберите Clay, slowly / 10:00, помощь не отмечайте. Через Tab перейдите к кнопке, нажмите Enter один раз. Пока видно Sending your request..., кнопка отключена. Дождитесь Reserved for Morgan Vale, настоящей Confirmation SS-… и понятного названия 10:00 в Submission result. Пассивное role="status" никогда не фокусируется; обычный раздел результата может получить фокус для удобного поиска заголовка и содержания.

Откройте Studio records, сверьте новую запись целиком, сохраните Existing Visitor и Avery Reed. Старый демонстрационный ID не должен быть принятой записью. Успешной HTTP-передачи мало: статус должен реально быть 201, подтверждение — из принятого booking. Самопроверка: настоящий принятый результат существует, ожидающая отправка запрещает повторные активации, затем кнопка восстанавливается.

Свяжите отказ сервера с правильным полем

Превратите реальный отказ проверки сервера в читаемую ошибку поля, не стирая остальные данные.

Браузер отклоняет неверную почтовую синтаксическую форму, но у сервиса есть бизнес-правило: тренировочные адреса оканчиваются на @example.test. morgan@other.test синтаксически верен, достигает сервера и получает HTTP 422. Это реальный запрос и отказ, не встроенная подсказка обязательного поля.

Ответ ошибки имеет следующий вид; это данные для чтения, не команда:

{
  "message": "Please correct the marked fields. Nothing was booked.",
  "errors": {
    "email": "Use an address ending in @example.test for this practice."
  }
}

errors.email относится к ключу API email. Сопоставляйте через form.elements[name], а не по порядку или первому абзацу ошибки. Строковый ключ в квадратных скобках выбирает свойство. Object.keys(body.errors) даёт массив имён; for (const name of ...) обходит их. Объявленную с let переменную можно переназначать, поэтому first запоминает первое отмеченное поле.

textContent показывает строки как текст, не интерпретируя сообщение как HTML. aria-invalid="true" отмечает неверное поле; связанный email-error даёт видимую причину через имеющийся aria-describedby. Не заменяйте постоянную подсказку одним цветом. Очистка старых ошибок перед запросом не позволяет старому тексту описывать исправленное поле. При 422 сначала покажите общий результат, затем сфокусируйте первое отмеченное поле. В остальных исходах допустим фокус обычного раздела результата. Пассивное живое объявление — отдельный элемент, не получающий принудительный фокус.

Успех использует шаблонную строку: обратные кавычки обрамляют текст, ${booking.id} подставляет текущее свойство. Полученную строку textContent показывает буквально. Подтверждение взято из объекта ответа, не придумано локально.

В Workspace замените feedback.js этими функциями:

cd /home/labex/project/stillspoke
cat > feedback.js <<'JS'
function clearErrors() {
  for (const field of document.querySelectorAll('form [name]')) {
    field.removeAttribute('aria-invalid');
    const error = document.getElementById(field.name + '-error');
    if (error) error.textContent = '';
  }
}
function showMessage(text) {
  const panel = document.querySelector('#result');
  document.querySelector('#message').textContent = text;
  panel.hidden = false;
  panel.focus();
}
function showResponse(status, body) {
  if (status === 201) {
    const booking = body.booking;
    showMessage(`Reserved for ${booking.name}. Confirmation ${booking.id}. ${booking.sessionTitle}.`);
  } else if (status === 422) {
    showMessage(body.message);
    let first;
    for (const name of Object.keys(body.errors)) {
      const field = document.querySelector('form').elements[name];
      document.getElementById(name + '-error').textContent = body.errors[name];
      field.setAttribute('aria-invalid', 'true');
      if (!first) first = field;
    }
    first.focus();
  } else {
    showMessage(body.message);
  }
}
JS

Вернитесь в Studio, обновите, введите Morgan Vale, morgan@other.test, Clay, slowly / 10:00, выбранную помощь и A seat near the entrance. Отправьте один раз. Должны появиться общее Nothing was booked и новая почтовая ошибка. Фокус переходит в Email, подпись и помощь остаются, как имя, сеанс, флажок и заметка. Studio records не должен получить запись отклонённого адреса. Чтобы посмотреть список без ухода из черновика, откройте ссылку Studio records на другой странице через Open Link in New Tab браузера, затем вернитесь в исходную форму.

Исправьте только адрес на morgan@example.test, отправьте снова. Старая ошибка исчезает, появляется подлинное новое подтверждение, заметка попадает в принятую запись. Проверьте и другое поле: при остальных верных данных замените имя ровно двумя пробелами. Встроенный minlength="2" считает набранные символы, но серверное правило удаляет внешние пробелы и только затем требует два. Реальный 422 должен отметить и сфокусировать Your name, не всегда Email, сохранив почту, сеанс и заметку. Верните Morgan Vale. Повторное использование синтетического имени во второй принятой записи разрешено; это не система уникальных адресов. Старые посетители остаются.

Если случайно ввести morgan.example.test без @, браузер откажет до POST. Нативное сообщение ожидаемо, но ветку 422 не проверяет. Замените на нативно верный morgan@other.test для серверной проверки. Если после правки нет обновления, используйте cat feedback.js в Workspace, сравните полный блок, исправьте опечатку и обновите Studio. Не удаляйте данные и не перезапускайте сервис ради JavaScript-опечатки. Самопроверка: настоящий ключ сервера связан с нужным полем и читаемой ошибкой, остальной ввод сохранён, исправление и повторная отправка работают.

Восстановитесь после заполнения сеанса и безопасной паузы

Различите полный сеанс и отказ до принятия, сохраните черновик и выполните безопасный повтор.

В Studio обновите и введите Casey Brook, casey@example.test, Clay, slowly / 14:00, выбранную помощь и A seat near the entrance. Перед отправкой прочитайте 14:00 в списке. Первая C выбирает первый совпадающий Clay, следующая переходит ко второму. Читайте видимый выбор, не предполагая постоянную позицию клавиши.

Отправьте один раз. Сервис возвращает HTTP 409 из-за заполнения. Прочитайте That session is full. Nothing was booked; choose another session. Ввод остаётся, кнопка доступна снова. Не удаляйте посетителя 14:00 ради места. Для 409 не нужен неправильный адрес или формат: это бизнес-отказ корректному предложению.

Измените только сеанс на Clay, slowly / 10:00. Practice room открывается отдельно, оставляя исходную форму. Это тренировка, отделённая от клиентской регистрации. Выберите Pause next submission, дождитесь Next submission will be refused before a booking is accepted. Закройте тренировочную страницу или вернитесь к исходной Studio. Не заменяйте открытый черновик ссылкой Back to signup тренировочной страницы.

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

Отдельная Practice room с безопасным управлением отказом

Отправьте исходную форму один раз. Теперь ответ HTTP 503 явно говорит Nothing was booked. Service paused; please retry. Имя, адрес, 10:00, флажок и заметка остаются. Запрос потребляет паузу. С теми же значениями ещё раз нажмите Enter на Reserve my place. Дождитесь настоящего 201, затем сопоставьте Casey Brook, почту, 10:00 и заметку в Studio records. Все прежние посетители и принятые записи сохраняются.

Контракт 503 узок: отказ происходит до принятия, поэтому повтор безопасен. Потеря связи или нечитаемый ответ — другой случай. catch означает неизвестное принятие, не Nothing was booked. Откройте Studio records в другой странице. Совпадение помогает найти подтверждение, но повторные имена и значения не всегда определяют попытку-создателя. Не отправляйте снова, пока исходная попытка не разрешена. Даже успешное чтение без совпадения не доказывает отказ: исходный запрос ещё может выполняться. Дождитесь разрешения или обратитесь к оператору студии (преподавателю в упражнении). Если список тоже недоступен, дождитесь и сервиса. Только явно предшествующий принятию отказ 503 устанавливает здесь безопасность повтора. Идемпотентность не обещается, распределённый платёжный процесс не изучается.

Для обычного исправления файла переключитесь в Workspace и покажите сохранённый запрос:

cd /home/labex/project/stillspoke
cat request.js

Проверьте точный относительный URL api/bookings, написание JSON.stringify, оба await и блок finally. Если их испортила опечатка, замените файл полным блоком шага 1, обновите Studio. Перед новой отправкой разрешите предыдущие неизвестные попытки согласно границе выше; исправление опечатки не доказывает отказ старому запросу. Когда известно, что попытка разрешена, повторите нужную нормальную проверку с новым синтетическим именем. Проверка только читает, не устанавливает другой обработчик и не делает реальную запись для исправления работы.

Самопроверка: полный сеанс и пауза дают видимый результат после ожидания, сохраняют черновик, позволяют исправление или безопасный повтор и не показывают выдуманных номеров принятия. Эквивалентные понятные формулировки допустимы. Нормальная принятая запись существует, вся подготовленная вместимость и посетители сохранены.

Резюме

Вы отправили JSON через POST с ожиданием ответа, отличили HTTP-отказ от неопределённости соединения, защитили ожидающую отправку и показали фактическое подтверждение. Вы связали ошибки с полями, сохранили ввод при заполнении и паузе и использовали записи для восстановления, не считая каждый сетевой сбой доказательством отсутствия принятия.