Introduction
Les libellés, la note conditionnelle et la mise en page adaptative de Still & Spoke sont prêts. Le gestionnaire actuel peut afficher une confirmation de démonstration sans envoyer de réservation. Vous le remplacerez par une requête asynchrone réelle et des retours reflétant ce que le studio a effectivement accepté. Une page apparemment correcte n'est pas une réservation tant que le service préparé n'a pas renvoyé sa confirmation.
Vous devez connaître les formulaires HTML, la mise en page CSS de base, les objets, fonctions, sélection du DOM et événements JavaScript. Ce lab enseigne JSON, async/await, fetch, les résultats HTTP, l'état en attente et les retours permettant la récupération. La sémantique et les contraintes conditionnelles sont préparées ici ; le lab précédent est utile, mais ses fichiers VM ne sont pas requis. Modifiez uniquement /home/labex/project/stillspoke/request.js et feedback.js. Conservez les autres fichiers, l'API, la capacité et les visiteurs. Les identités sont fictives ; aucun courriel ni paiement n'est envoyé. Les 60 minutes sont une estimation de l'auteur, pas une mesure auprès de débutants.
Utilisez Workspace pour remplacer les fichiers complets et Studio pour le site. Study form est un exemple fonctionnel indépendant ; ses réservations ne terminent jamais votre page. Studio records lit les réservations acceptées. Practice room est un espace distinct pouvant refuser une requête avant toute acceptation. Chaque lab dispose de données indépendantes. Ce lab n'enseigne ni le backend préparé, ni le déploiement en production, ni l'idempotence de requêtes arbitraires, ni une conformité d'accessibilité complète.
Envoyez une requête réelle et attendez sa réponse
Dans cette étape, remplacez la démonstration par un vrai POST, empêchez les activations multiples pendant son attente et affichez une confirmation authentique du serveur.
Dans Studio, choisissez Study form. Avec Tab, atteignez Your name et saisissez Example Guest ; Tab vers Email, puis sample@example.test ; Tab vers Session, tapez C et lisez Clay, slowly / 10:00. Passez avec Tab la case non cochée et la note facultative jusqu'à Reserve my place. Appuyez une fois sur Enter. Attendez la véritable Confirmation SS-…, puis choisissez Studio records et comparez nom, courriel et horaire. Revenez avec Back to signup et Work form. L'exemple utilise une autre base. Votre page contient toujours uniquement les visiteurs initiaux ; son texte de démonstration ne prouve pas une acceptation.
L'exemple montre le résultat attendu : une confirmation avec son propre titre et un horaire lisible. Votre numéro sera différent.

L'API préparée appartient à cet environnement local. Voici ses champs et règles publics :
| Champ JSON | Signification |
|---|---|
name |
Au moins deux caractères après suppression des espaces extérieurs. |
email |
Adresse fictive finissant par @example.test. Le navigateur vérifie d'abord la syntaxe native. |
session |
ID stable publié : clay-morning pour 10:00 ou clay-afternoon pour 14:00. L'après-midi est déjà complet. |
support |
Booléen indiquant si l'assistance de placement est sélectionnée. |
note |
Chaîne d'au moins cinq caractères avec assistance, facultative sinon. |
Un POST envoie une proposition de réservation à api/bookings. Cette URL est relative à la page courante de travail ou d'exemple, chacune restant dans son propre espace métier. L'API renvoie du JSON, format textuel pour objets, chaînes, nombres et booléens. Les noms de propriétés et chaînes JSON utilisent des guillemets doubles. JSON.stringify(data) transforme l'objet JavaScript en texte JSON ; response.json() analyse la réponse pour en faire un objet. L'en-tête Content-Type: application/json précise le contenu du corps. Ni le texte du frontend ni un bouton désactivé n'implémentent la règle de capacité du service.
Les statuts HTTP distinguent les résultats :
| Statut | Réponse et fait établi |
|---|---|
201 |
booking contient les champs acceptés, le sessionTitle lisible et l'id généré par le serveur. Une réservation est acceptée ; sa confirmation commence par SS-. Ne l'inventez jamais. |
422 |
errors associe les champs aux explications. Aucune réservation ; corrigez les champs signalés. |
409 |
La séance est complète. Aucune réservation ; choisissez-en une disponible. |
503 |
La pause contrôlée a refusé avant acceptation. Aucune réservation ; une nouvelle tentative est sûre. |
fetch renvoie une Promise, représentant un résultat à venir. Un gestionnaire async peut employer await pour suspendre son propre travail jusqu'à ce que la Promise aboutisse ou échoue, sans bloquer l'utilisation du navigateur. Attendez la réponse HTTP, puis l'analyse JSON. Un refus HTTP comme 422 ou 503 résout aussi fetch ; il ne déclenche pas automatiquement catch. Testez explicitement le statut. Un échec réseau ou d'analyse peut atteindre catch sans établir si la réservation a été acceptée. Le message doit indiquer une acceptation inconnue et proposer Studio records pour la récupération.
L'événement submit couvre le bouton et Enter dans un champ. event.preventDefault() empêche la navigation d'envoi normale afin d'envoyer JSON. Les champs nativement invalides bloquent l'envoi avant que l'événement atteigne le gestionnaire. form.elements retrouve les contrôles par name, pas leur position visible ni leur id HTML. Les textes utilisent .value, la case .checked.
Définissez pending avant toute attente. La garde retourne immédiatement si un autre envoi arrive pendant l'attente ; .disabled désactive visiblement le bouton natif. Cela empêche les activations répétées sur cette page pour une requête en cours, sans garantie contre d'autres pages ou des clients concurrents arbitraires. try effectue la requête ; catch signale l'acceptation inconnue en cas d'échec ; finally rétablit le bouton et efface l'avis passif dans tous les cas. Conservez les valeurs plutôt que réinitialiser le formulaire.
clearErrors, showMessage et showResponse sont des fonctions de feedback.js. Après analyse HTML, la page charge avec defer, dans cet ordre, scenario.js, validity.js, feedback.js et request.js. Le premier fournit les données, le deuxième la règle native et les deux derniers coopèrent pour la requête et les retours. Le fichier de retour actuel gère un 201 normal ; vous améliorerez les refus de champ ensuite.
Dans Workspace, utilisez cd pour choisir le répertoire. cat > request.js remplace ce seul fichier. Le marqueur here-document JS entre guillemets rend le contenu littéral jusqu'à la ligne finale contenant seulement JS ; collez le bloc complet sans accents graves Markdown. Le retour de l'invite signifie que le fichier est enregistré.
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
Revenez dans Studio, actualisez, saisissez Morgan Vale, morgan@example.test, choisissez Clay, slowly / 10:00 et laissez l'assistance décochée. Appuyez sur Tab jusqu'au bouton puis une fois sur Enter. Pendant Sending your request..., le bouton est désactivé. Attendez que Submission result montre Reserved for Morgan Vale, une véritable Confirmation SS-… et le titre lisible de 10:00. L'avis passif role="status" ne reçoit jamais le focus ; la section de résultat ordinaire peut le recevoir pour rendre son titre et son résultat faciles à trouver.
Choisissez Studio records, comparez la réservation entière et préservez Existing Visitor et Avery Reed. L'ancien ID de démonstration ne doit pas apparaître comme réservation acceptée. Une transmission HTTP réussie ne suffit pas : le statut doit réellement être 201 et la confirmation provenir du booking accepté. Autocontrôle : un vrai enregistrement accepté existe, l'envoi en cours empêche les activations répétées et le bouton est ensuite rétabli.
Associez le refus serveur au bon champ
Dans cette étape, transformez un refus serveur réel en erreur lisible du champ sans effacer les autres saisies.
Le navigateur peut refuser une syntaxe de courriel incorrecte, mais le service impose aussi une règle métier : les adresses de pratique finissent par @example.test. morgan@other.test a une syntaxe valide, atteint le serveur et produit HTTP 422. Il s'agit d'une vraie requête refusée, distincte du message natif de champ obligatoire.
Une réponse d'erreur a cette forme ; ce sont des données à lire, pas une commande :
{
"message": "Please correct the marked fields. Nothing was booked.",
"errors": {
"email": "Use an address ending in @example.test for this practice."
}
}
errors.email désigne la clé API email. Retrouvez-la via form.elements[name], pas l'ordre des contrôles ou le premier paragraphe d'erreur. Une clé chaîne entre crochets sélectionne la propriété correspondante. Object.keys(body.errors) produit un tableau de noms ; for (const name of ...) parcourt chaque nom. Une variable déclarée avec let peut être réaffectée : first mémorise ainsi le premier champ signalé.
textContent affiche les chaînes comme texte, sans interpréter le message serveur comme HTML. aria-invalid="true" marque le champ invalide ; le paragraphe associé email-error apporte un motif visible grâce à l'aria-describedby existant. Ne remplacez pas l'aide permanente par la couleur. Effacer les erreurs avant une nouvelle requête évite qu'un ancien message décrive un champ corrigé. Pour 422, affichez le résultat global puis ciblez le premier champ signalé. Pour les autres résultats, la section ordinaire peut recevoir le focus. L'annonce passive d'état en direct est un élément distinct qui ne doit pas être ciblé de force.
Le succès utilise une chaîne de gabarit : les accents graves entourent le texte et ${booking.id} insère la valeur courante. Cette chaîne est affichée littéralement par textContent. La confirmation vient de l'objet réponse, pas d'un numéro inventé localement.
Dans Workspace, remplacez feedback.js par ces fonctions :
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
Revenez dans Studio, actualisez et saisissez Morgan Vale, morgan@other.test, Clay, slowly / 10:00, assistance cochée et A seat near the entrance. Envoyez une fois. Vous devez voir Nothing was booked globalement et une nouvelle erreur spécifique au courriel. Le focus passe à Email ; son libellé et son aide restent présents, tout comme nom, séance, case et note. Studio records ne doit pas ajouter de réservation pour l'adresse refusée. Pour lire les enregistrements sans quitter le brouillon, ouvrez le lien Studio records dans une autre page avec Open Link in New Tab du navigateur, puis revenez au formulaire initial.
Corrigez seulement l'adresse en morgan@example.test et renvoyez. L'erreur précédente disparaît, une confirmation authentique apparaît et la note atteint l'enregistrement. Testez aussi un autre champ : les autres valeurs étant valides, remplacez le nom par exactement deux espaces. minlength="2" natif compte ces caractères saisis, mais la règle publiée supprime les espaces extérieurs avant d'exiger deux caractères. Le vrai 422 doit signaler et cibler Your name, pas toujours Email, en conservant courriel, séance et note. Rétablissez Morgan Vale. Réutiliser ce nom fictif dans une deuxième réservation est autorisé ; cet exercice n'impose pas l'unicité des adresses. Les visiteurs existants restent présents.
Si vous tapez accidentellement morgan.example.test sans @, le navigateur refuse avant POST. Ce message natif est normal mais ne teste pas 422. Utilisez morgan@other.test, syntaxiquement valide, pour tester le serveur. Si le retour ne change pas après modification, utilisez cat feedback.js dans Workspace, comparez le bloc complet, corrigez la faute puis actualisez Studio. Ne videz pas les données et ne redémarrez pas le service pour corriger une faute JavaScript. Autocontrôle : la clé serveur réelle est associée au bon champ et à son erreur lisible, les autres saisies survivent et correction puis renvoi fonctionnent.
Gérez une séance complète et une pause sûre
Dans cette étape, distinguez une séance complète d'un refus avant acceptation, conservez le brouillon et terminez une nouvelle tentative sûre.
Dans Studio, actualisez et saisissez Casey Brook, casey@example.test, Clay, slowly / 14:00, assistance cochée et A seat near the entrance. Lisez le titre 14:00 dans le sélecteur avant d'envoyer. Une première C sélectionne la première option Clay correspondante ; une autre passe à la suivante. Lisez le choix affiché plutôt que supposer une position fixe pour une touche.
Envoyez une fois. Le service renvoie HTTP 409 car cette séance est complète. Lisez That session is full. Nothing was booked; choose another session. Les saisies restent et le bouton redevient disponible. Ne supprimez pas le visiteur de 14:00 pour libérer une place. Le 409 ne nécessite ni adresse invalide ni requête mal formée : c'est un refus métier d'une proposition correcte.
Changez uniquement la séance pour Clay, slowly / 10:00. Choisissez Practice room ; une autre page s'ouvre pour conserver le formulaire initial. C'est un espace de formation séparé de l'inscription des clients. Choisissez Pause next submission et attendez Next submission will be refused before a booking is accepted. Fermez la page de pratique ou revenez au formulaire Studio initial. N'utilisez pas son lien Back to signup pour remplacer le brouillon encore ouvert.
La page distincte annonce sa garantie de refus avant acceptation. Gardez le formulaire initial ouvert pendant cette action.

Envoyez une fois le formulaire initial. Le service renvoie cette fois HTTP 503 et déclare explicitement Nothing was booked. Service paused; please retry. Nom, adresse, séance de 10:00, case et note restent. La pause est consommée par cette requête. Avec les mêmes saisies, appuyez encore une fois sur Enter sur Reserve my place. Attendez le véritable 201, puis comparez dans Studio records Casey Brook, le courriel, 10:00 et la note. Les visiteurs et réservations antérieures restent présents.
Le contrat du 503 est étroit : il refuse avant de réserver, ce qui rend cette nouvelle tentative sûre. Une connexion perdue ou une réponse illisible est différente. catch indique une acceptation inconnue, pas Nothing was booked. Ouvrez alors Studio records dans une autre page. Une correspondance peut retrouver une confirmation, mais des noms et valeurs répétés n'identifient pas toujours la tentative qui l'a créée. Ne renvoyez pas tant que la tentative initiale reste non résolue. Même une lecture réussie sans correspondance ne prouve pas le refus : la requête peut encore être en cours. Attendez sa résolution ou demandez à l'opérateur du studio (au formateur dans cet exercice). Si les enregistrements sont illisibles, attendez aussi le service. Seul le refus 503 explicitement antérieur à l'acceptation prouve ici qu'une nouvelle tentative est sûre. Ce lab ne garantit pas l'idempotence et n'enseigne pas un paiement distribué.
Pour une erreur d'édition ordinaire, passez dans Workspace et affichez le fichier sauvegardé :
cd /home/labex/project/stillspoke
cat request.js
Vérifiez l'URL relative exacte api/bookings, JSON.stringify, les deux expressions await et le bloc finally. Si une faute les a modifiés, remplacez le fichier avec le bloc complet de l'étape 1, puis actualisez Studio. Avant tout nouvel envoi, résolvez les tentatives inconnues selon la limite ci-dessus ; corriger une faute ne prouve pas qu'une ancienne requête a été refusée. Une fois cette tentative connue comme résolue, refaites le test normal pertinent avec un autre nom fictif. Le contrôle en lecture seule n'installe pas de gestionnaire de remplacement et n'envoie pas de réservation réelle pour réparer votre travail.
Autocontrôle : complet et pause donnent un résultat visible après l'attente, conservent le brouillon, autorisent correction ou nouvelle tentative sûre et n'affichent jamais de faux numéro accepté. Une formulation équivalente compréhensible convient. Une réservation normale acceptée existe toujours, et l'état préparé de capacité et des visiteurs reste intact.
Résumé
Vous avez envoyé du JSON par POST en attendant la réponse, distingué refus HTTP et incertitude réseau, protégé l'activation pendant l'attente et affiché la véritable confirmation acceptée. Vous avez associé les erreurs aux champs, conservé la saisie après séance complète ou pause et utilisé les enregistrements pour orienter la récupération sans supposer qu'une connexion échouée signifie qu'aucune réservation n'a été acceptée.
