显示服务器实际接受的结果

JavaScriptBeginner
立即练习

介绍

Still & Spoke 的标签、条件座位备注和响应式布局已经准备好。当前作品处理程序可能在未发送报名时显示演示确认。你将把它替换为真实异步请求,以及反映工作室实际接受结果的反馈。页面看起来正确,并不代表已报名;必须由准备好的服务返回确认。

你需要了解 HTML 表单、基本 CSS 布局、JavaScript 对象、函数、DOM 选择和事件。本实验教授 JSON、async/await、fetch、HTTP 结果、等待状态与可恢复反馈。表单语义和条件约束在这里已准备好;完成上一个实验有帮助,但不需要它的 VM 文件。只编辑 /home/labex/project/stillspoke/request.js 和 feedback.js。保留其他前端文件、API、容量及已有访客。所有身份均为合成数据,不会发送邮件或收取费用。60 分钟是作者估计,不是测量得到的初学者结果。

在 Workspace 中完整替换文件,在 Studio 中使用网站。Study form 是独立且可用的示例,其报名永远不能完成作品页面。Studio records 读取已接受报名。Practice room 是独立训练页,可在接受任何报名之前拒绝一个请求。每个实验从独立数据开始。本实验不教授准备好的后端、生产部署、任意请求幂等性或完整无障碍合规。

发送一个真实请求并等待结果

这一步用真正的 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 告诉服务请求体包含什么。前端文字或禁用按钮都没有实现服务的容量规则。

HTTP 响应状态区分不同结果:

状态 响应及其证明的事实
201 booking 包含已接受字段、可读的 sessionTitle 和服务器生成的 id。报名已接受;确认编号以 SS- 开头。绝不要编造。
422 errors 把字段名映射到说明。没有报名;修正标记字段。
409 所选场次已满。没有报名;选择有空位的场次。
503 受控训练暂停在接受之前拒绝请求。没有报名;可以安全重试。

fetch 返回 Promise,表示稍后才得到的结果。async 事件处理程序可用 await 暂停自身工作,直到 Promise 完成,同时浏览器仍可使用。先等待 HTTP 响应,再等待 JSON 解析。422 或 503 这样的 HTTP 拒绝仍会让 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 反引号。shell 提示符返回时文件已保存。

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... 可见期间,按钮禁用。等待 Submission result 显示 Reserved for Morgan Vale、真实 Confirmation SS-… 和可读 10:00 标题。被动 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 不应新增被拒绝地址的报名。如需查看记录又不离开草稿,使用浏览器的 Open Link in New Tab 命令在另一浏览器页打开 Studio records,然后返回原表单页。

只把地址改为 morgan@example.test,再次提交。旧字段错误清除,真实新确认出现,备注进入已接受记录。再测试另一种字段拒绝:其他输入有效时,把姓名替换为恰好两个空格。原生 minlength="2" 计算键入字符,但公开服务器规则会先去除首尾空白,再要求两个字符。真实 422 必须标记并聚焦 Your name,不能总是 Email,同时保留邮箱、场次和备注。之后恢复 Morgan Vale。允许在第二次已接受报名中重复使用该合成姓名;此练习不是邮箱唯一性系统。原有访客保留。

若误输入缺少 @ 的 morgan.example.test,浏览器会在 POST 前拒绝。这种原生消息符合预期,却没有测试 422 分支。换成原生语法有效的 morgan@other.test 才能测试服务器规则。如果编辑代码后反馈始终不更新,在 Workspace 用 cat feedback.js 查看,与完整块比较,修正拼写后刷新 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 场次、复选框和备注均保留。该请求消耗一次暂停。在原输入仍保留的情况下,再在 Reserve my place 上按一次 Enter。等待真实 201 确认,再在 Studio records 中核对 Casey Brook、邮箱、10:00 时间和座位备注。原访客及此前已接受报名都保留。

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。任何新提交前,先按上述边界解决先前的未知尝试;单纯修正拼写不能证明之前的请求被拒绝。确认该尝试已解决后,用新合成姓名重复相关正常测试。只读实验检查不会安装替代处理程序,也不会提交真实报名来修复作品。

自检:满额和暂停结果会在等待结束后给出可见反馈,保留草稿,支持修正或安全重试,绝不显示伪造接受编号。等效且易懂的错误措辞也可以。正常已接受记录仍存在,准备好的容量与访客状态全部保持不变。

总结

你通过等待完成的 POST 发送 JSON,区分 HTTP 拒绝与连接不确定性,防止等待期间重复激活,并显示实际接受确认。你把服务器字段错误与控件关联,在满额和暂停结果中保留输入,并用记录引导恢复,而不是假定每次连接失败都代表没有接受报名。