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

Beitrag lesen

problematische Seite

Hallo Rolf,

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.

das wusste ich nicht, wäre aber sehr wichtig für mich gewesen! 😄

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

Warum gruselig?

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.

manageError bekommt als Parameter das errors-Array von validate übergeben und gibt gar nichts zurück, sondern pusht einfach eine Fehlermeldung hinein (oder auch nicht). Der Grund dafür ist, dass ich nicht vor jede rule in der validate-Funktion ein push schreiben wollte, sondern dies an einer Stelle erledigt haben wollte. Mehr macht manageError eigentlich gar nicht, außer sich eben noch darum zu kümmern, ob eine Fehlermeldung angezeigt wird oder nicht, und zwar durch die Funktionen addErrorMessage und removeErrorMessage. Auch hier wollte ich mir sparen, dies bei jeder einzelnen rule hinzuschreiben, sondern einfach alles an einer Stelle haben.

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.

Hm, das weiß ich nicht. Ich sehe jedenfalls keine Dopplung.

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.

Das hatte leider nicht funktioniert, da der return im if statement nichts gebracht hat.
So funktioniert es jedoch:

let skip;
Promise.all( errors ).then( errors => {
	errors.forEach( ( error, index ) => {
		if ( error && !skip ) {
			form.querySelectorAll( 'input' )[index].focus();
			skip = true;
		}
	});
	if ( !skip ) {
		form.dataset.status = 'ok';
		form.requestSubmit( e.submitter );
	}
});

Mit elem.dataset greifst Du auf die data-Attribute eines HTML Elements zu.

Damit hatte ich schon gearbeitet.

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.

Das hat sehr gut geklappt, danke! Und auch danke für die Erklärung!

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).

Diese Promise-Geschichte ist sehr abstrakt für mich. Da bräuchte ich deutlich mehr Zeit, um es auch nur ansatzweise zu durchdringen. 😵


Ich habe nicht verstanden, was du mit deinem Vorschlag „alles async zu machen“ konkret meintest, aber Folgendes habe ich jetzt getan, damit es funktioniert:

Die validate- und die manageError-Funktion habe ich als async deklariert werden. Vor jedes fetch musste ein await. Ebenso musste vor jeden manageError-Aufruf in der validate-Funktion ein await.

Ich glaube, das war’s.

Wahrscheinlich meintest du das nicht, aber zumindest läuft das script jetzt so.

Grüße
Boris