Asynchrone Events, Callback und Promise

Asynchrone Bearbeitung

Javascript kann Aufgaben »nebenbei« abhandeln – während der Browser eine Seite lädt oder Javascript bereits eine Funktion ausführt, kann das Script auf das Eintreten eines Ereignisses (z.B. auf den Klick auf einen Button) reagieren.

23-02-02 SITEMAP

Asynchrone Verarbeitung und Callback

Asynchrones Scripting ist das Fundament für dynamische Webseiten, aber das Jonglieren zwischen quasi simultanen Requests und die Auswahl zwischen den Optionen erscheinen schnell unüberschaubar. Javascript führt eine Handvoll eingebauter Funktionen asynchron aus. Dazu gehören z.B. der XMLHTTPRequest, setTimeout und requestAnimationFrame. Fast immer soll das Script zusätzliche Funktionen oder Methoden nach dem Eintreten des Events und Abwicklung der asynchronen Tasks ausführen.

Callback-Funktionen sind die altgediente Methode, mit der Javascript asynchrone Aktionen ausführt. Einer Javascript-Funktion können andere Funktionen als Argument übergeben werden, weil Funktionen »Objekte erster Ordnung« sind. Die Callback-Funkion wird aufgerufen, sobald die Anweisungen der ersten Funktion abgearbeitet sind und läuft dann als zweite Funktion.

Das ist wie ein Aufruf, wenn das Telefon auf der anderen Seite besetzt ist und ein automatischer Rückruf ausgelöst wird, sobald die Leitung auf der anderen Seite frei ist.

Ein ganz einfaches Beispiel für eine asynchrone Funktion mit Callback-Funktion ist setTimeout (callback, delay).
setTimeout (function () {
	console.log ("Hallo Welt");
}, 5000);

console.log ("Javascript sagt");
Javascript sagt
Hallo Welt

Obwohl setTimeout im Script vor der Consolen-Ausgabe "Javascript sagt" steht, erscheint zuerst "Javascript sagt", weil setTimeout 5 Sekunden wartet, bevor die Callback-Funktion ausgeführt wird.

Callback-Funktionen lösen Probleme wie "Wir wissen nicht, wann ein Bild geladen ist, aber wollen danach direkt etwas mit dem Bild machen".

function loadImage (src, callback) { 
   console.log ("loading image");
   let img = document.createElement('img');
   img.src = src;
   img.onload = () => callback(img);
}

loadImage("kaffeekochen.webp", img => {
   console.log ('Bild ist geladen');
});

Javascript Promise

So weit, so gut. Aber was passiert, wenn das Bild nicht geladen werden kann? Oder wenn nicht ein Bild, sondern gleich mehrere Bilder geladen werden?

Das kommt oft vor: In Abhängigkeit vom Erfolg eines ersten Events soll ein weiteres Event behandelt werden. Hier setzt das Javascript Promise ein: Die Funktion wird in ein Versprechen (wenn … dann … sonst) gepackt.

new Promise (function (resolve, reject) {
   let img = document.createElement('img');
   img.src= 'image.jpg';
   img.onload = resolve();
   img.onerror = reject();
})
.then ( loadingSuccess () )
.catch ( loadingFailed () );

function loadingSuccess() {
	console.log ("Bild geladen.");
}

function loadingFailed () {
	console.log ("Bild konnte nicht geladen werden. Mach was anderes.");
}



		

Async / Await

await ist ein Operator, der dafür sorgt, dass der Aufruf abwartet, ob ein Promise erfüllt (resolve) oder abgelehnt (reject) wurde. Dieser Operator kann nur innerhalb von asynchronen Funktionen verwendet werden.

async als Teil der Deklaration einer Funktion gibt an, dass der Code asynchron ausgeführt werden soll. await vor einer Anweisung gibt ein Promise zurück, dass die Funktion anhält und auf das Ergebnis wartet, bevor die Ausführung fortgesetzt wird.

const getBooks = async (num) => {
	try {
		let response = await fetch ("file.json"); // fetch ist asynchron
		let json = await response.json ();        // aber wartet dank await auf die Antwort
		let list = json.list;
		console.log (list[num].autor); // Dritter Autor in der Liste
	} catch (e) {
		console.log ("Daten wurde nicht geladen", e);
	}
}

getBooks (2);


			

Oder ein Bild laden, Breite und Höhe abfragen und dann ein img-Element einsetzen.

Die Methode HTMLImageElement.decode() entspricht einem onload mit Promise (gefunden auf async await in image loading auf Stackoverflow).

(async () => {
  const img = new Image();
  img.src = "spices.jpg";
  await img.decode();
  // genug gewartet, Bild geladen!
  console.log( "width: " + img.naturalWidth + " height: " + img.naturalHeight );
  document.querySelector("#viewbox").innerHTML = "<img src='" + img.src + "' width='" + "img.naturalWidth'" + " height='" + img.naturalHeight + "'>";
})();