Code

js/views/register.js — strength and async

In the code column — js/views/register.js, the most complex form in the course. Three new things.

1. Password strength. checkPassword (from validation.js) returns a score 0–4 and the gaps:

export function checkPassword(password) {
  let score = 0; const errors = [];
  if (password.length >= 8) score++; else errors.push('At least 8 characters.');
  if (/[a-z]/.test(password) && /[A-Z]/.test(password)) score++; else errors.push('Mix of case.');
  if (/\d/.test(password)) score++; else errors.push('A number.');
  if (/[^A-Za-z0-9]/.test(password)) score++; else errors.push('A symbol.');
  return { score, label: ['Very weak','Weak','Fair','Good','Strong'][score], errors };
}

On input, the meter bar and the checklist update live — the user watches the strength climb.

2. Confirmation matching — cross-field, checked live. matches(values.password, ...) is a rule. But there's a subtlety: if you already entered the confirmation and then change the first password, the confirmation must re-validate immediately:

passwordInput.addEventListener('input', () => {
  const confirm = form.querySelector('[name="confirmPassword"]');
  if (confirm.value) {
    setFieldError(form, 'confirmPassword',
      confirm.value === passwordInput.value ? '' : 'Passwords do not match.');
  }
});

3. Async check. "Is the username taken?" on blur:

usernameInput.addEventListener('blur', async () => {
  usernameInput.classList.add('checking');            // a spinner
  const taken = await isUsernameTaken(value);         // a simulated server request
  usernameInput.classList.remove('checking');
  if (taken) setFieldError(form, 'username', `"${value}" is already taken.`);
});

Verify. In the register form:

1. Type a password → the meter climbs, requirements get ✓.
2. Confirmation doesn't match → error; fix the first password → the error clears live.
3. Enter "admin" and leave the field → after ~half a second: "already taken".

Gotcha (async races). Async checks can race: type "admin" (a slow check in flight), then "adm002" (a fast check returns first). Unguarded, an older response can overwrite a newer one and show an error for the wrong name. Real fixes: debounce + "latest wins" (by a request sequence number). In lesson 17 we'll see how types help model that "loading / loaded / error" state safely.