JavaScript/WindowOrWorkerGlobalScope/fetch/Daten empfangen
Den grundlegenden Datenempfang mit dem Fetch-API haben wir bereits im Überblicks-Beispiel auf der Fetch-Startseite gezeigt.
Dieses Beispiel soll nun genauer betrachtet werden.
async function getJsonData(url) {
const response = await fetch(url);
if (response.ok)
return response.json();
else
throw new Error(`Wetterdaten konnten nicht geladen werden, ` +
`HTTP-Status ${response.status}`);
}
}
async function updateWetterdaten(station) {
try {
const url = new URL("https://weather.example.com/getData.php");
url.searchParams.set("station", station);
const wetter = await getJsonData(url);
showData(station, wetter);
}
catch (fehler) {
showError(station, fehler);
}
}
Inhaltsverzeichnis
Schritt 1: Senden des HTTP-Requests
In einem HTTP-Request sind mehrere Teile zu unterscheiden.
- Die URL, an die der Request gesendet wird
- HTTP Request-Header für diverse Optionen und Steuerungsfunktionen
- Optional ein Datenblock, der zum Server hochgeladen wird
Die URL ist das, was als Mindestanforderung an fetch übergeben werden muss. In unserem Beispiel ist es der Beispielserver https://weather.example.com, an den der Request gesendet wird und der die HTTP Anfrage
GET /getData.php?station=42
erhält. GET deshalb, weil das das HTTP Verb ist, das fetch standardmäßig verwendet.
Um die URL-Parameter zu setzen, ist es ratsam, ein URL-Objekt und dessen searchParams-Eigenschaft zu verwenden. Das vermeidet Fehler beim korrekten Maskieren und Aufbauen der Parameter.
Nach der GET Zeile sendet der Browser noch einige Zeilen mit Request-Headern. Wir haben im Beispiel keine angegeben, aber der Browser schickt dennoch einige mit. Dies ist vor allem der Host-Header, ohne den gemanagte Serverfarmen mit vielen Webseiten auf einem Serer nicht möglich wären, aber auch andere technische Header wie Accept, Accept-Encoding, Referer oder User-Agent. Wir müssen uns um diese Header nicht kümmern, solange wir mit den Standardwerten zufrieden sind.
Um zu sehen, was der Browser alles sendet, kannst Du die Entwicklerwerkzeuge des Browsers öffnen und jeden einzelnen HTTP-Request auf dem Netzwerk-Tab inspizieren.
Nachdem fetch() die GET-Zeile und die Request-Header erfolgreich an den Server übertragen hat, gibt es dem Aufrufer ein Promise zurück und endet. Dieses Promise ist zumeist im pending-Zustand, kann aber auch sofort rejected sein, wenn die URL oder andere fetch-Parameter verhindern, dass ein Senden möglich ist. Wenn mit dem Senden begonnen werden kann, es aber bei der Verbindungsaufnahme zum Server Schwierigkeiten gibt, wird das Promise asynchron rejected.
Ein Grund für einen sofortigen Reject kann sein, dass fetch mit einer ftp://-URL aufgerufen wird. Das FTP-Protokoll wird von fetch nicht unterstützt.
getJsonData muss nun abwarten, bis sich das Promise auf ein Ergebnis festlegt. Das könnten wir mit Hilfe von then() tun, moderner ist aber die Verwendung von await in einer async-Funktion. Damit endet getJsonData mit Rückgabe eines Promises.
Übergeben von Daten
Bei einem GET-Request ist die Übergabe von Daten nur über die URL möglich. Man kann sie direkt in die URL einbauen, muss dann aber selbst auf alle Regeln für eine korrekte URL achten, oder man lässt sich von einem URL- oder URLSearchParams-Objekt dabei helfen.
URLs sollten nicht zu lang werden. Bei umfangreicheren Daten wird deshalb POST oder PUT angewendet, worauf wir im Artikel Daten senden eingehen.
Die Übergabe mit GET lässt sich aus dem updateWetterdaten-Beispiel erkennen. Das URLSearchParams-Objekt bietet mehrere Möglichkeiten, Parameter in den query-Teil der URL einzusetzen - näheres dazu findet sich auf der Referenzseite zu URLSearchParams.
Wie man die übergebenen Daten am Server verarbeitet, ist ein anderes Thema. Wie es mit PHP geht, beschreiben wir im PHP-Tutorial zum Auswerten von Formularen. Für den Server ist es ziemlich gleichgültig, ob er eine Formularantwort bekommt oder einen fetch-Request. HTTP ist HTTP…
Schritt 2: Erhalten der HTTP-Response
Wir haben die getJsonData()-Funktion an dieser Stelle verlassen:
const response = await fetch(url);
Wenn das Promise erfüllt (fulfilled) wird, wird sie mit einem Response-Objekt fortgesetzt, das in der Varianlen response gespeichert wird. Darin findest Du:
- den HTTP-Status der Antwort (
response.status) - den Klartext zum Status (
response.statusText) - die Information, ob der Status im Bereich 200-299 liegt (
response.ok) - die URL, auf die letztendlich zugegriffen wurde (
response.url) - etliche Methoden zum Datenabruf
Die ok-Eigenschaft ist sehr bequem, andernfalls müssten wir mühsam if (response.status >= 200 && response.status < 300) programmieren.
Die url-Eigenschaft ist dann von Interesse, wenn die Möglichkeit von Redirects besteht. fetch() erkennt, wenn der Server per 3xx-Status mitteilt, dass die gewünschte Ressource anderswo zu finden ist, und führt automatisch einen Folgerequest mit der Adresse durch, die der Server über den Location-Header gemeldet hat.
Zu diesem Zeitpunkt befinden sich die Daten, die der Server bei der Anwort mitgeschickt hat, noch auf der Leitung oder im Empfangspuffer des Browsers. Wir wissen lediglich an Hand der Content-Type und Content-Length-Header, was und wieviel da auf uns wartet. Mittels response.headers.get("Content-Type") könnten wir den Typ der Antwort erkennen und darauf reagieren.
Unser Beispiel verzichtet darauf – was etwas leichtfertig ist. Es geht schlicht davon aus, vom Server eine JSON-kompatible Zeichenkette zu erhalten. Kommt etwas anderes, wird das Script abbrechen.
Schritt 3: Abrufen der Daten
Bis jetzt haben wir nur den Teil der Antwort eingelesen und verarbeitet, der zum HTTP-Protokoll gehört. Der vom Server gesendete Inhalt fehlt noch.
Unser Beispiel enthält die Zeilen
if (response.ok)
return response.json();
else
throw new Error(`... HTTP-Status ${response.status}`);
Wenn also response.ok den Wert true liefert (d. h. der HTTP-Status im 200er Bereich liegt), rufen wir response.json() auf und geben das, was wir da erhalten, an den Aufrufer zurück.
Ist response.ok nicht wahr, dann kann das unterschiedliche Ursachen haben, sie führen aber alle dazu, dass die gewünschten Daten nicht geliefert wurden. D. h. eine tiefer gehende Analyse des HTTP-Status ist im JavaScript-Programm selten sinnvoll, eine Protokollierung des Fehlers aber schon, damit man der Ursache auf den Grund gehen kann. Deswegen wird in diesem Fall ein Error-Objekt geworfen. Werfen eines Fehlers in einer async-Funktion führt wie in einem onFulfilled-Handler dazu, dass das zugehörige Promise auf rejected gesetzt wird.
json() ist eine der sechs Methoden des Response-Objekts, mit denen man eine Serverantwort auslesen kann. Sie liest den Content-Teil der Serverantwort, interpretiert ihn als JSON und erzeugt daraus ein Objekt. Weil zum Lesen des Contents auch der Datenempfang über das Netzwerk gehört und dies möglicherweise länger dauert, arbeitet json() wiederum asynchron und gibt ein Promise zurück.
Der komplette Ablauf aus Datenempfang und Konvertieren des JSON-Strings in ein Objekt geschieht so, dass die JavaScript-Umgebung davon nicht aufgehalten wird; sie kann währenddessen auf Benutzeraktionen oder andere Ereignisse reagieren. Sobald die Daten in der gewünschten Form verfügbar sind, erfüllt sich das zurückgegebene Promise mit dem erhaltenen Wert. Falls es beim Empfang zu einem Fehler kam (beispielsweise Serververbindung bricht ab oder der Server liefert keinen JSON-String), wird das Promise mit einem SyntaxError-Objekt rejected.
Wir könnten in getJsonData() mit await auf das Objekt warten und es dann zurückgeben. Das ist aber unnötig, denn der Aufrufer dieser Funktion muss ohnehin auf die asynchrone Antwort dieser Funktion warten. Deshalb können wir das Promise zurückgeben, so dass der Aufrufer das fertige Objekt (oder die Fehlermeldung) direkt empfangen kann.
Wenn Dir nicht ganz klar ist, wie sich Promises durch andere Promises erfüllen lassen, empfehlen wir Dir unseren Einführungsartikel zu Promises.