AJAX
als Präsentation ▻Wir kennen schon die Funktionsweise von HTTP. Bisher wurde ein HTTP Request durch eine Handlung der UserIn ausgelöst (URL eintippen, Link anklicken), oder um Ressourcen zu laden die zu einem HTML-Dokument gehören.
Mit AJAX lernen wir nun eine neue Art kennen, wie HTTP-Request verwendet werden: Asynchrone Requests.
▻Was ist AJAX?
AJAX ist die englische Abkürzung für „Asynchrones Javascript und XML“. In diesem Kapitel lernen Sie was das genau bedeutet, und dass sich hinter dem X zum Schluss auch andere Format verbergen können
▻AJAX Beispiel: Autocomplete
Ein Beispiel für die Verwendung von AJAX ist das in der Abbildung unten
gezeigte Eingabefeld:
schon während des Eintippens eines Suchwortes wird eine Anfrage an den Webserver
geschickt. Dieser antwortet mit einer Liste von vorgeschlagenen Namen. Diese Liste
wird mit Javascript in einer div
unterhalb des Eingabefelds angezeigt:
Mit AJAX wird hier eine HTTP-Anfrage gesendet.
Asynchron heisst hier: der Request wird abgesetzt, das Javascript-Programm läuft sofort weiter, die UserIn kann weiterhin mit der Webseite interagieren. Erst wenn die Antwort des Servers vorliegt starte ein Javascript-Programm und fügt die Daten in die Seite ein.
▻Im Javascript Programm: synchorn
Nehmen wir an rechnen1
, rechnen2
, rechnen3
, rechnen4
,
und synchronLaden
sind Funktionen, die wir in einem anderne Teile des
Programmes definiert haben.
Bei jedem Aufruf einer der Funktionen passiert dasselbe:
- erst wenn
rechnen1
fertig ist geht’s weiter mitrechnen2
, - erst wenn
rechnen2
fertig ist geht’s weiter mitsynchronLaden
, - erst wenn
synchronLaden
fertig ist geht’s weiter mitrechnen3
, - erst wenn
rechnen3
fertig ist geht’s weiter mitrechnen4
.
Dieser Ablauf ist “synchron”.
Javascript Code synchron
rechnen1(); rechnen2(); data = synchronLaden(url); console.log("data arrived", data); rechnen3(); rechnen4();
Wenn nun die Funktion synchronLaden
wirklich Daten von der url
mittels HTTP lädt, dann kann das sehr lange dauern - vielleicht
eine ganze Sekunde?
Im Javascript Programm: asynchorn
Es gibt nun in Javascript die Möglichkeit Funktionen zu schreiben die sich anders verhalten, nämlich asynchron.
Hier ein Beispiel mit mehreren synchronen Funktionen rechnen1
, rechnen2
, rechnen3
, rechnen4
,
und einer asynchronen Funktion genannt asynchronLaden
:
Javascript Code asynchron
function handleData(data) { console.log("data arrived", data); } rechnen1(); rechnen2(); asynchronLaden(url, handleData); rechnen3(); rechnen4();
Erst wenn rechnen1
fertig ist geht’s weiter mit rechnen2
. Erst wenn rechnen2
fertig ist geht’s weiter mit asynchronLaden
- aber dann passiert etwas Besonderes:
asynchronLaden
will etwas sehr zeitaufwendiges tun - Daten von der URL per HTTP laden.
Trotzdem scheint es sehr schnell zu gehen, das Programm geht direkt zu rechnen3
weiter, egal ob und wie schnell der Server auf die HTTP-Anfrage antwortet. Wenn
rechnen3
fertig ist wird rechnen4
ausgeführt, und das JavaScript Programm endet.
Aber eine Sekunde später passiert was Neues: Die Daten, die in asynchronLaden
angefragt wurden sind eingelangt - das ist irgendwie im Hintergrund passiert - und sind
nun bereit zur Weiterverarbeitung.
Wenn die Daten vom Server schließlich einlangen wird die Funktion handleData
aufgerufen und die Daten zu verarbeiten.
Synchroner Ablauf wird nie unterbrochen
Achtung: ein laufendes Javascript Programm wird nie unterbrochen.
Im letzten Code-Beispiel wird es nie passieren, dass zwischen rechnen3
und rechnen4
etwas anderes (z.B. handleData
) passieren!
Erst wenn dieses kleine Programm fertig durchgelaufen ist
stellt sich die Frage ob das Laden der Daten über HTTP fertig,
und damit handleData
dran ist.
Javascript Code asynchron
function handleData(data) { console.log("data arrived", data); } rechnen1(); rechnen2(); asynchronLaden(url, handleData); rechnen3(); rechnen4();
Asynchrone HTTP Requests
Betrachten wir nun den Ablauf für ein Textfeld mit Autocomplete-Funktion, wie in der obigen Abbildung gezeigt. Folgende Abbildung ist ein Sequenz Diagramm, die Zeit läuft von oben nach unten.
Zuerst wird die Webseite mit dem Formular geladen: der Browser schickt die Anfrage an den Server und erhält eine Antwort. Was immer zuvor im Browser angezeigt wurde wird - nach Ankunft des HTTP Response - gelöscht, die neue Seite wird im Browser dargestellt. Diese Verhalten des Browsers ist uns schon bekannt.
Nun kommt der neue Teil: das Eintippten des ersten Buchstabens ins Eingabefeld löst ein Javascript-Programm aus, das einen AJAX-Request absetzt. Am Netzwerk ist das ein ganz normaler HTTP Request, für den Server gibt es keinen Unterschied zu jedem anderen Request.
Was anders ist, ist das Verhalten des Browsers: Das Absenden des Requests bleibt die Webseite bestehen und bleibt interaktiv - das Absenden passiert meist von der UserIn unbemerkt.
Wenn die Daten des Response einlangen wird nicht die Seite gelöscht, sondern es wird eine Javascript-Funktion in der Seite aufgerufen, die die Daten entgegen nimmt. Für das Autocomple-Verhalten bestehen die Daten aus einer Liste von Vorschlägen, die Javascript-Funktion zeigt diese Vorschläge unterhalb des Eingabefeldes an.
▻Datenformate - mehr als nur XML
Das X am Ende von AJAX steht für XML – das stimmt aber nicht: die Daten vom Server können im XML-Format gesendet werden, aber genauso auch als HTML oder reiner Text oder JSON. Man könnte das X in AJAX auch als „X-beliebiges Format“ deuten.
▻Simples Javascript Beispiel
Im ersten AJAX Beispiel wird der Output eines PHP-Counters in eine HTML-Seite
eingebunden. Für den HTTP Request verwenden wir den Befehl fetch
mdn.
Html Code Counter einbinden mit Javascript
<html> <head> <meta charset="utf-8"> <title>AJAX counter</title> </head> <body> <h1>Webseite</h1> <div id="output">0</div> <script> fetch("counter_ajax.php") .then((response) => { console.log("Response wird empfangen"); console.log(response.url); console.log(response.headers.get('Content-Type')); let promiseOfText = response.text(); return promiseOfText; }) .then((text) => { console.log("text wurde aus dem Response herausgelesen", text); document.getElementById('output').innerHTML = text; console.log("fertig!"); }); console.log("abgesendet, sofort weiter"); </script> </body> </html>
Diesen Ablauf müssen wir genauer analysieren.
▻Promises
Der Rückgabewert der Funktion fetch
ist eine Promise: ein Objekt,
das den Umgang mit einer asynchrone Operation einfacher machen soll.
<script> let promise = fetch("counter_ajax.php"); </script>
Das Promise Objekt ist ein Platzhalter für das Ergebnis der Operation, das noch nicht bekannt ist. In anderen Programmiersprachen ist die Promise auch als Future oder deferred Task bekannt, siehe wikipedia.
Mit der Methode then()
kann eine Funktion als Callback angegeben
werden, die aufgerufen wird wenn das Ergebnise vorliegt:
promise.then((response) => { console.log("Response wird empfangen"); // tu was mit dem response objekt });
fetch
Die Funktion fetch
kann nicht nur für einfache GET Requests eingesetzt werden,
sondern bietet noch mehr Möglichkeiten:
Sollen Cookies bei diesem Request mitgeschickt werden?
<script> let promise = fetch("api.php", { credentials: 'same-origin' }); </script>
Beliebige zusätzliche HTTP Header einfügen.
<script> let promise = fetch("api.php", { headers: { "API-Key": "xxxxyyyy", }); </script>
POST Request mit Daten im JSON Format:
<script> let promise = fetch("api.php", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data), }); </script>
Response Body
Bei fetch
muss mit dem Ergebnis einer asynchronen Operation
eine weitere asynchrone Operation aufgerufen werden: Das Laden des
gesamten Body des HTTP Response kann lange dauern. Deswegen
erhalten wir nicht sofort den body, sondern wieder eine Promise:
promise.then((response) => { console.log("Response wird empfangen"); let promiseOfText = response.text(); return promiseOfText; });
Es gibt drei Arten wie der Body ausgelesen werden kann - als text, json oder binary blob (z.B. beim Laden eines Bildes).
response.text(); response.json(); response.blob();
Chaining
Da nun die erste Callback Funktion wieder eine Promise zurückgibt,
kann wieder die Methode then
verwendet werden:
.then((response) => { console.log("Response wird empfangen"); let promiseOfText = response.text(); return promiseOfText; }) .then((text) => { console.log("text wurde aus dem Response herausgelesen", text); document.getElementById('output').innerHTML = text; console.log("fertig!"); }); console.log("abgesendet, sofort weiter");
Kurze Version
Wenn man alle Ausgaben auf die Console weg lässt wird der Code sehr kurz:
fetch("counter_ajax.php") .then(response => response.text() ) .then(text => document.getElementById('output').innerHTML = text );
Fehlerbehandlung
Für die Fehlerbehandlung gibt es die Methode catch
:
fetch("counter_ajax.php") .then(response => response.text()) .then(text => document.getElementById('output').innerHTML = text ) .catch(error => { document.getElementById('output').innerHTML = '#'; console.log(error); });
Fehlerbehandlung von HTTP
Zur Erinnerung: Bei einem HTTP Response wird ein Statuscode mitgeliefert, der Erfolg oder Fehler anzeigen kann, siehe auch http-status-code.de
Achtung: wenn bei einem fetch
der HTTP-Response einen Statuscode
für einen Fehler liefert, z.B. 404 oder 500 , dann
löst das noch keine Exception aus, die mit catch
gefangen werden könnte.
Die Property response.ok
zeigt an ob der HTTP Status im
postivien Bereich (200-299) war.
Das müsste man selbst behandeln:
button.disabled = true; fetch("counter_ajax.php") .then(response => { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return response.text(); }) .then(text => document.getElementById('output').innerHTML = text ) .catch(error => { document.getElementById('output').innerHTML = '#'; console.log(error); }) .finally { button.disabled = false; };
Backend
Das Backend ist im Fall des Counters sehr simpel - ein PHP-Programm das eine Zahl ausgibt. Im einfachsten Fall also:
<?php echo 42; ?>