vorige Präsentation: JavaScript und HTTP | zurück zum Buch-Kapitel [esc] | Nächste Präsentation Async/Await
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.
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
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.
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:
rechnen1
fertig ist geht’s weiter mit rechnen2
,rechnen2
fertig ist geht’s weiter mit synchronLaden
,synchronLaden
fertig ist geht’s weiter mit rechnen3
,rechnen3
fertig ist geht’s weiter mit rechnen4
.Dieser Ablauf ist “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?
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
:
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.
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.
function handleData(data) { console.log("data arrived", data); } rechnen1(); rechnen2(); asynchronLaden(url, handleData); rechnen3(); rechnen4();
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.
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.
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> <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.
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 });
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>
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();
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");
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 );
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); });
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; };
Das Backend ist im Fall des Counters sehr simpel - ein PHP-Programm das eine Zahl ausgibt. Im einfachsten Fall also:
<?php echo 42; ?>
vorige Präsentation: JavaScript und HTTP | zurück zum Buch-Kapitel [esc] | Nächste Präsentation Async/Await
/
#