Rolf B: JS-Code wird zu früh ausgeführt (fetch, async, await)

Beitrag lesen

problematische Seite

Hallo borisbaer,

du trittst Dir mit der Asynchronität auf die Füße, genau. Sobald Du asynchron prüfst, kannst Du den Submit nur noch abbrechen. Denn Du kannst im Eventhandler für submit nicht auf das Promise-Ergebnis warten - es geht nicht, beim Versuch stündest du Dir in der Taskqueue selbst im Weg.

Ein Mix aus sync und async macht die Sache richtig gruselig, daher wäre mein Vorschlag, alles async zu machen.

1. Lass validate grundsätzlich ein Promise zurückgeben. Wenn validate den Fehler synchron ermitteln kann, dann gib eins zurück, das schon resolved ist.

function validate(...) {
   if (user.isIdiot)
      return Promise.resolve("Du bist ein Idiot!");
   else
      return Promise.resolve(null);
}

Wenn Du dagegen asynchron prüfen musst, dann lass validate ein Promise zurückgeben, dass zur Fehlermeldung oder null resolved - je nach Prüfergebnis.

Wie Du das in der Interaktion zwischen validate und manageError machst, ist mir nicht ganz klar (ich mag grad nicht deinen Source studieren), aber da wirst Du in validate ein Promise aus manageError brauchen. Gib einfach das Ergebnis der Promise-Kette aus manageError zurück, das sollte passen. Und mich deucht, dass Du da die Fehlerbehandlung irgendwie in validate und manageError gedoppelt haben könntest. Mit einem Promise, das aus manageError zurückkommt, solltest Du das verbessern können. In validate dürfte dann dies hier stehen - nimm es als Pseudocode:

function validate(...) {
   
   if ( /* Feld mit asynchroner Prüfung */ ) {
      return manageError(...)
             .then(error => { // Ist vermutlich ein Text oder Null
                // Fehler zeigen oder löschen
                return error;    
             });
   }
}

2. Im submit-Handler lässt Du die Validierungen durchlaufen, und dann gehst Du mit Promise.all(errors) her und wartest darauf, dass die Promises sich erfüllen. Dieser Wartevorgang ist immer asynchron, auch wenn schon alle erfüllt sind, d.h. du musst den Submit jetzt erstmal mit preventDefault abbrechen. Die Fortsetzung erfolgt im .then-Handler des Promise.all.

form.addEventListener( 'submit', event => {
   if ( form.dataset.status == 'ok' )
      return;

   const promisedErrors = [];
   if ( form.name === 'sign-in' ) {
      promisedErrors.push( validate( form.username, [ { rule: 'required' } ] ) );
      promisedErrors.push( validate( form.password, [ { rule: 'required' } ] ) );
   }
   event.preventDefault();

   Promise.all(promisedErrors)
   .then(errors => {
      errors.forEach( ( error, index ) => {
         if ( error ) {
            form.querySelectorAll( 'input' )[index].focus();
            return;
         }
      });
      form.dataset.status = "ok";
      form.requestSubmit(event.submitter);
   });
});

Mit elem.dataset greifst Du auf die data-Attribute eines HTML Elements zu. Ich verwende hier data-status, um den Erfolgsstatus der Prüfung zu speichern. Das könnte man natürlich auch irgendwie anders lösen, aber speichern muss man das, denn im then-Handler von Promise.all musst Du, wenn die Prüfungen okay waren, den Submit erneut auslösen. Und dafür nimmt man nicht form.submit(), sondern form.requestSubmit(), weil man damit den Submitter retten kann (also den Button, der den Submit auslöste). Nur löst requestSubmit noch ein weiteres Mal das submit-Event aus, daher ist ein Flag nötig, das nach erfolgreicher Validation dafür sorgt, dass der Submit sofort durchgeht. Danach schickt der Browser das Form neu und das data-Attribut ist wieder weg. Anders wäre es, wenn Du den Submit per Ajax behandelst und das form-Element stehen lässt, dann müsstest Du den Status selbst wieder löschen.

Soeben gelernt: Promise.all hat die Eigenschaft, nicht länger zu warten, wenn ein Promise rejected. In manageError tust Du das, wenn der fetch fehlschlägt. In dem Fall würdest Du kein Validation-Ergebnis herausbekommen, sondern nur den einen fetch-Fehler. Wenn Du das nicht willst, schau Dir mal Promise.allSettled an (bei MDN, im Selfwiki steht's nicht).

Rolf

--
sumpsi - posui - obstruxi