Asynchrone Events, Callback und Promise

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.

Asynchrone Bearbeitung

Synchrone Aktionen vs. asynchrone Verarbeitung

JavaScript ist eine synchrone Programmiersprache: Der Script-Code wird Zeile für Zeile, zu einer Zeit immer nur eine Anweisung ausgeführt. Synchrone Aktionen bilden eine lineare Kette und sind einfach nachvollziehbar:

  • Mathematische Operationen wie
    let res = 17 + 4; console.log ("res", res)
  • Zuweisungen an Variablen
    const a = 17; const b = 4; const sum = a + b; 
  • String-Operationen
    const strg = "Hallo"; let start = strg + " Welt!"; console.log (start)
  • Funktionsaufrufe
    let betrag = summe(a, b)

JavaScript führt aber auch viele Funktionen asynchron aus – besser: läßt sie ausführen, denn diese Aufgaben liegen nicht im nativen Javascript, sondern gehören zu den sogenannten Web Application Programming Interface. Asynchrone Funktionen lauern sozusagen in einem Hinterzimmer, das »Call Stack« genannt wird, und werden vom Browser zu gegebner Zeit zur Ausführung gebracht. Dazu gehören z.B.

  • der XMLHTTPRequest,
  • fetch,
  • setTimeout
  • requestAnimationFrame
  • addEventListener
Call Stack und Web API

Für die Behandlung asynchroner Aufgaben stellt Javascript mehrere Optionen zur Verfügung:

  1. Callback-Funktionen
  2. Promise
  3. Async / Await

Asynchrone Verarbeitung mit Callback-Funktionen

Fast immer soll das Script zusätzliche Funktionen oder Methoden nach dem Eintreten des Events und Abwicklung der asynchronen Tasks ausführen. Bei einem Klick-Event soll das Script dem Benutzer weitere Informationen anzeigen, wenn eine Datei im Hintergrund geladen wurde, soll das Script eine Tabelle mit den Daten anzeigen.

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 + "'>";
})();
Suchen auf mediaevent.de