Introdução
Os rótulos, a observação condicional e o layout responsivo da Still & Spoke estão prontos. O manipulador atual pode mostrar confirmação de demonstração sem enviar inscrição. Você o substituirá por uma solicitação assíncrona real e mensagens que reflitam o que o estúdio aceitou. Uma página com aparência correta só é uma inscrição quando o serviço preparado devolve a confirmação.
Você precisa de formulários HTML, layout CSS básico, objetos, funções, seleção do DOM e eventos JavaScript. Este laboratório ensina JSON, async/await, fetch, resultados HTTP, estado pendente e mensagens com recuperação. Semântica e restrições condicionais estão preparadas; o laboratório anterior ajuda, mas seus arquivos de VM não são necessários. Edite somente /home/labex/project/stillspoke/request.js e feedback.js. Preserve os demais arquivos, API, capacidade e visitantes. Todas as identidades são sintéticas; não há email ou pagamento. Os 60 minutos são estimativa do autor, não medição com iniciantes.
Use Workspace para substituir arquivos completos e Studio para o site. Study form é um exemplo independente funcional; suas inscrições nunca concluem o trabalho. Studio records lê inscrições aceitas. Practice room é uma página separada que pode recusar uma solicitação antes de aceitar qualquer coisa. Cada laboratório começa com dados independentes. Não ensina o backend preparado, implantação em produção, idempotência de solicitações arbitrárias ou conformidade completa de acessibilidade.
Envie uma solicitação real e espere a resposta
Substitua a demonstração por um POST real, evite ativações duplicadas durante a espera e mostre uma confirmação autêntica do servidor.
Em Studio, escolha Study form. Vá com Tab a Your name e digite Example Guest; Tab a Email, sample@example.test; Tab a Session, digite C e leia Clay, slowly / 10:00. Passe com Tab pela assistência desmarcada e pela nota opcional até Reserve my place. Pressione Enter uma vez. Espere a Confirmation SS-… real, abra Studio records e compare nome, email e hora. Retorne por Back to signup e Work form. O exemplo tem outro banco. Sua página ainda tem apenas visitantes existentes; o texto de demonstração não estabelece aceitação.
O exemplo mostra a confirmação esperada com título próprio e horário legível. Seu número será diferente.

A API preparada pertence ao ambiente local. Seus campos e regras públicos são:
| Campo JSON | Significado |
|---|---|
name |
Pelo menos dois caracteres após remover espaços externos. |
email |
Endereço sintético terminado em @example.test. O navegador verifica primeiro a sintaxe nativa. |
session |
ID estável publicado: clay-morning para 10:00 ou clay-afternoon para 14:00. A tarde está cheia. |
support |
Booleano indicando seleção de assistência de assento. |
note |
String com ao menos cinco caracteres com assistência, opcional no outro caso. |
Um POST envia uma proposta de inscrição a api/bookings. É uma URL relativa à página atual de trabalho ou exemplo, portanto cada uma permanece em seu espaço de negócio. A API retorna JSON, formato textual de objetos, strings, números e booleanos. Nomes de propriedades e strings JSON usam aspas duplas. JSON.stringify(data) converte o objeto JavaScript em texto JSON; response.json() interpreta a resposta como objeto. O cabeçalho Content-Type: application/json informa o conteúdo do corpo. Nem o texto do frontend nem um botão desativado implementam a capacidade do serviço.
Os status HTTP distinguem os resultados:
| Status | Resposta e fato estabelecido |
|---|---|
201 |
booking contém os campos aceitos, sessionTitle legível e id gerado pelo servidor. Houve aceitação; a confirmação começa com SS-. Nunca a invente. |
422 |
errors associa nomes de campos a explicações. Nada foi inscrito; corrija os campos marcados. |
409 |
A sessão está cheia. Nada foi inscrito; escolha uma disponível. |
503 |
A pausa controlada recusou antes da aceitação. Nada foi inscrito; repetir é seguro. |
fetch retorna uma Promise, representando um resultado futuro. Um manipulador async usa await para suspender seu próprio trabalho até a Promise terminar com sucesso ou falha, enquanto o navegador continua utilizável. Espere a resposta HTTP e depois a interpretação JSON. Uma recusa HTTP como 422 ou 503 também resolve fetch; não entra automaticamente em catch. Verifique o status explicitamente. Falha de rede ou interpretação pode entrar em catch sem estabelecer se houve aceitação. A mensagem deve declarar aceitação desconhecida e o caminho de recuperação Studio records.
O evento submit cobre o botão e Enter em um campo. event.preventDefault() impede o envio normal com navegação para permitir JSON. Campos nativamente inválidos param o envio antes do manipulador. form.elements busca por name, não posição visível ou HTML id. Texto usa .value; a caixa usa .checked.
Defina pending antes de qualquer espera. A proteção retorna imediatamente se outro envio chegar durante a espera; .disabled desativa visivelmente o botão nativo. Impede ativações repetidas nesta página durante uma solicitação em andamento, sem garantir isso contra outras páginas ou clientes concorrentes arbitrários. try faz a solicitação; catch informa aceitação desconhecida na falha; finally restaura o botão e limpa o aviso passivo em qualquer resultado. Preserve valores em vez de reiniciar o formulário.
clearErrors, showMessage e showResponse são funções de feedback.js. Após analisar o HTML, a página carrega com defer, nesta ordem, scenario.js, validity.js, feedback.js, request.js. O primeiro fornece dados, o segundo a regra nativa e os últimos cooperam no envio e nas mensagens. O arquivo atual atende o 201 normal; você melhorará recusas de campo depois.
Em Workspace, use cd para escolher o diretório. cat > request.js substitui só esse arquivo. O marcador here-document JS entre aspas deixa o texto literal até a linha com apenas JS; cole todo o bloco sem os acentos graves Markdown. O retorno do prompt indica que foi salvo.
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
Volte a Studio, atualize, digite Morgan Vale, morgan@example.test, escolha Clay, slowly / 10:00 e deixe a assistência desmarcada. Vá com Tab ao botão e pressione Enter uma vez. Durante Sending your request..., ele fica desativado. Espere Submission result mostrar Reserved for Morgan Vale, Confirmation SS-… autêntica e o título legível 10:00. O aviso passivo role="status" nunca recebe foco; a seção comum de resultado pode recebê-lo para facilitar encontrar título e conteúdo.
Escolha Studio records, compare a nova inscrição completa e preserve Existing Visitor e Avery Reed. O ID antigo de demonstração não deve aparecer como registro aceito. Transferência HTTP bem-sucedida não basta: a resposta precisa realmente ser 201, e a confirmação vir do booking aceito. Autoverificação: existe registro real, uma solicitação pendente bloqueia ativações repetidas e o manipulador restaura o botão depois.
Associe a recusa do servidor ao campo certo
Transforme uma recusa de validação real em erro de campo legível sem apagar outras entradas.
O navegador pode recusar sintaxe de email inválida, mas o serviço também tem uma regra de negócio: endereços de prática terminam em @example.test. morgan@other.test é sintaticamente válido, chega ao servidor e gera HTTP 422. É uma solicitação e recusa reais, distintas da mensagem nativa de campo obrigatório.
Uma resposta de erro tem esta forma; são dados para ler, não um comando:
{
"message": "Please correct the marked fields. Nothing was booked.",
"errors": {
"email": "Use an address ending in @example.test for this practice."
}
}
errors.email aponta à chave API email. Faça a associação por form.elements[name], não pela ordem nem pelo primeiro parágrafo de erro. Uma chave de string entre colchetes seleciona a propriedade correspondente. Object.keys(body.errors) produz um array de nomes; for (const name of ...) percorre cada um. Uma variável let permite reatribuição, então first guarda o primeiro campo marcado.
textContent exibe as strings como texto, sem interpretar mensagens do servidor como HTML. aria-invalid="true" marca um campo inválido; email-error associado fornece motivo visível pelo aria-describedby existente. Não troque a ajuda persistente por cor apenas. Limpar erros antes de novo envio impede uma mensagem velha de descrever um campo corrigido. No 422, mostre o resultado geral e depois foque a primeira entrada marcada. Outros resultados podem focar a seção comum. O anúncio passivo de status ao vivo é outro elemento e não deve ser focado à força.
O sucesso usa template literal: acentos graves envolvem o texto e ${booking.id} insere o valor atual. Isso constrói uma string que textContent mostra literalmente. O número vem da resposta, não é inventado localmente.
Em Workspace, substitua feedback.js pelas funções:
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
Volte a Studio, atualize, preencha Morgan Vale, morgan@other.test, Clay, slowly / 10:00, assistência marcada e A seat near the entrance. Envie uma vez. Deve aparecer Nothing was booked geral e um novo erro específico do email. O foco vai a Email; rótulo e ajuda permanecem, assim como nome, sessão, caixa e nota. Studio records não deve adicionar inscrição para o endereço recusado. Para olhar registros sem sair do rascunho, abra Studio records em outra página pelo comando Open Link in New Tab do navegador e volte ao formulário original.
Corrija apenas para morgan@example.test e envie de novo. O erro antigo é limpo, aparece confirmação autêntica e a nota chega ao registro. Teste também outro campo: com o restante válido, substitua o nome por exatamente dois espaços. minlength="2" nativo conta esses caracteres, mas a regra publicada remove espaços externos antes de exigir dois. O verdadeiro 422 deve marcar e focar Your name, não sempre Email, mantendo email, sessão e nota. Restaure Morgan Vale. É permitido repetir o nome sintético em outra inscrição aceita; não é um sistema de unicidade de email. Visitantes antigos permanecem.
Se digitar sem querer morgan.example.test sem @, o navegador recusa antes de POST. É esperado, mas não testa a ramificação 422. Use morgan@other.test, válido para o navegador, para testar a regra do servidor. Se as mensagens não mudam após edição, use cat feedback.js em Workspace, compare o bloco completo, corrija a grafia e atualize Studio. Não apague dados nem reinicie o serviço por uma digitação JavaScript incorreta. Autoverificação: a chave real do servidor corresponde à entrada e ao erro legível certos, o restante sobrevive e corrigir e reenviar funciona.
Recupere-se de sessões cheias e de uma pausa segura
Distinga uma sessão cheia de uma recusa anterior à aceitação, mantenha o rascunho e faça uma repetição segura.
Em Studio, atualize e preencha Casey Brook, casey@example.test, Clay, slowly / 14:00, assistência e A seat near the entrance. Leia o título 14:00 antes de enviar. A primeira C seleciona a primeira opção Clay; outra C passa à seguinte. Leia o valor visível em vez de supor uma posição fixa para a tecla.
Envie uma vez. O serviço retorna HTTP 409 por lotação. Leia That session is full. Nothing was booked; choose another session. Entradas ficam e o botão volta a funcionar. Não remova o visitante 14:00 para abrir vaga. O 409 não precisa de email inválido nem formato incorreto: é uma recusa de negócio a uma proposta válida.
Altere só para Clay, slowly / 10:00. Escolha Practice room, que abre outra página mantendo seu formulário. É treinamento separado da inscrição de clientes. Escolha Pause next submission e espere Next submission will be refused before a booking is accepted. Feche a prática ou volte ao Studio original. Não use Back to signup da prática para substituir seu rascunho ainda aberto.
A página separada declara a promessa de recusa antes da aceitação. Mantenha o formulário original aberto ao usar o controle.

Envie o original uma vez. Agora vem HTTP 503, explicitando Nothing was booked. Service paused; please retry. Nome, endereço, 10:00, caixa e nota ficam. A solicitação consome a pausa. Com as entradas preservadas, pressione Enter mais uma vez em Reserve my place. Espere o 201 real e confira em Studio records Casey Brook, email, 10:00 e observação. Visitantes e inscrições anteriores permanecem.
O contrato do 503 é restrito: recusa antes de aceitar, tornando essa repetição segura. Conexão perdida ou resposta ilegível é diferente. catch declara aceitação desconhecida, não Nothing was booked. Abra então Studio records em outra página. Um registro correspondente ajuda a localizar uma confirmação, mas nomes e valores repetidos nem sempre identificam a tentativa que o criou. Não envie novamente com a tentativa original pendente de resolução. Mesmo uma leitura bem-sucedida sem correspondência não prova recusa: a solicitação original ainda pode estar em andamento. Aguarde resolução ou consulte o operador do estúdio (instrutor nesta prática). Se os registros não puderem ser lidos, espere também pelo serviço. Só a recusa 503 explicitamente anterior à aceitação estabelece repetição segura aqui. Não há promessa de idempotência nem ensino de pagamentos distribuídos.
Para um erro comum de edição, mude para Workspace e mostre o arquivo salvo:
cd /home/labex/project/stillspoke
cat request.js
Confira a URL relativa exata api/bookings, a grafia JSON.stringify, os dois await e o bloco finally. Se houver erro, substitua pelo bloco completo da etapa 1 e atualize Studio. Antes de nova submissão, resolva tentativas desconhecidas conforme o limite acima; corrigir uma grafia não estabelece que a anterior foi recusada. Quando souber que está resolvida, repita o teste normal pertinente com outro nome sintético. A verificação somente leitura não instala substituto nem faz inscrição real para reparar sua obra.
Autoverificação: lotação e pausa produzem resultados visíveis após a espera, preservam o rascunho, permitem correção ou repetição segura e nunca mostram um número aceito inventado. Redação equivalente compreensível é válida. Uma inscrição normal aceita continua existindo, e a capacidade e os visitantes preparados seguem intactos.
Resumo
Você enviou JSON por POST aguardando a resposta, distinguiu recusa HTTP de incerteza de conexão, protegeu ativações pendentes e exibiu a confirmação realmente aceita. Associou erros aos campos, conservou entradas diante de lotação e pausa e usou registros para orientar recuperação sem supor que toda conexão falha significa que nada foi aceito.
