JavaScript/WindowOrWorkerGlobalScope/fetch/Daten empfangen

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

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.

Typischer fetch-Ablauf mit await
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 = `https://weather.example.com/getData.php?station=${station}`;
      const wetter = await getJsonData(url);
      showData(station, wetter);
   }
   catch (fehler) {
      showError(station, fehler);
   }
}

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/code> übergeben werden muss. In unserem Beispiel ist es der Beispielserver <code>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.

Auf die GET Zeile folgen einige Zeilen mit Request-Headern. Wir haben im Beispiel keine angegeben, aber der Browser schickt dennoch einige mit. Dies ist vor allem der Hostname 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 (die man beispielsweise im Netzwerk-Tab der Entwicklerwerkzeuge finden kann).

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. Das Promise kann in bestimmten Fehlersituationen sofort auf rejected stehen, z. B. wenn man die URL ftp://ftp.example.org/foo.txt abrufen will. Das FTP-Protokoll wird von fetch nicht unterstützt, darum der sofortige Reject.

Übergeben von Daten

Bei einem GET-Request ist die Übergabe von Daten nur über die URL möglich. Man kann sie, wie oben, direkt in die URL einbauen oder sich von einem URL- oder URLSearchParams-Objekt dabei helfen lassen.

URLs sollten nicht zu lang werden. Bei umfangreicheren Daten wird deshalb POST oder PUT angewendet, worauf wir im Artikel Daten senden eingehen.

Datenübergabe bei GET Request
async function getWetterJson(url, station) {
   const adresse = new URL(url);
   adresse.searchParams.set("station", station);
   const response = await fetch(url);
   if (response.ok)
      return response.json();
   else
      throw new Error(`Wetterdaten konnten nicht geladen werden, ` + 
                      `HTTP-Status ${response.status}`);
  }
}

Dadurch, dass man die übergeben URL direkt in ein URL-Objekt verpackt, kann der Aufrufer bereits eigene Parameter mitgeben und wir fügen nur noch die Station hinzu. Bei stumpfer String-Verkettung wären Zusatzparameter vom Aufrufer nicht möglich.

Schritt 1: Erhalten der HTTP-Response

Schau nochmal ins Beispiel. Da steht

   const response = await fetch(url);

Wenn das Promise erfüllt (fulfilled) wird, bekommen wir ein Response-Objekt zurück. Darin finden wir

  • 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 aber 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 2: Abrufen der Daten

Unser Beispiel enthält die Zeilen

   if (response.ok)
       return response.json();
   else
       throw new Error(`... HTTP-Status ${response.status}`);

Wenn also 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. 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. Das geschieht asynchron, so dass wir von ihr nicht direkt das Objekt bekommen, sondern nur ein Promise. Sobald es in den fulfilled Zustand übergeht, ist das deserialisierte Objekt verfügbar. Und wenn die Antwort kein korrekter JSON-String war, dann wird es mit einem SyntaxError rejected.

Wenn Du Dir das Verhalten von Promises schon durchgelesen hast (wenn nicht, hole es besser nach!), weißt Du, dass ein Promise A, das durch ein Promise B erfüllt wird, sich an Zustand und Wert von B koppelt. So ist es auch hier. Die getJsonData()-Funktion ist async, liefert also ein Promise für ihr Ergebnis zurück. Das geschieht in dem Moment, wo der fetch()-Aufruf mit seinem Promise zurückkehrt und der await Operator zum Zug kommt. Alles weitere in getJsonData erfolgt asynchron als Reaktion auf die Erfüllung des fetch-Promise. Wir könnten in getJsonData noch mittels await auf die Antwort von json() warten, aber da wir nicht vorhaben, mit dem Ergebnis irgendetwas zu tun, können wir auch gleich das Promise zurückgeben und den Rest dem Aufrufer überlassen.

Dazu gehört auch die Fehlerbehandlung. Ein Promise, auf das man mit await wartet und das zurückgewiesen wird, wirft eine Exception. Geschieht das innerhalb von getJsonData, wird das Promise der async-Funktion zurückgewiesen und führt dazu, dass der await des Aufrufers ebenfalls wirft oder sein .catch()-Callback aufgerufen wird. Jedenfalls hat der Aufrufer damit das Recht (und die Pflicht), auf den Fehler zu reagieren.