|

Gehaltene und gebrochene Versprechen

Deine AJAX-Requests sind unübersichtlich und verwinkelt?
Benutze Promises, um Deine Anfragen simpler zu machen und den Urwald aufzuräumen!

AJAX-Requests
AJAX steht für Asynchronous JavaScript and XML.
Asynchron bedeutet, dass Inhalte (z.B. Bilder oder JSON-Dateien) nach dem eigentlichen Laden der Seite mit Hilfe von JavaScript nachgeladen werden können. Das macht Webseiten dynamischer.
Mehr Infos zu AJAX findest du im MDN.

Promises

Ein Promise ist ein Objekt, das eine Verbindung zwischen einem Request und den dazugehörigen Event-Handlern herstellt. Es verspricht und stellt sicher, dass bestimmte Funktionen ausgeführt werden.

Ein Promise hat immer einen dieser Status:

  • pending wenn eine Antwort noch aussteht
  • fulfilled wenn der Request erfolgreich war
  • rejected wenn der Request fehlschlug

Der Struggle mit den Events ist real

„Kann man das nicht auch mit Events lösen?“

Ja, aber lange nicht so gut.
Während Events für immer wieder auftretende Ereignisse wie keydown oder click gut geeignet sind, sind sie bei asynchronen Requests schwer unter Kontrolle zu bekommen, denn man weiß nie genau, wann und wie oft der Event-Handler aufgerufen wird.

Event-Handler
Event-Handler sind JavaScript-Funktionen, die ausgeführt werden, wenn ein bestimmtes Ereignis (wie z.B. das oben erwähnte click-Ereignis) an einem Element auftritt.
Lerne mehr über Event-Handler bei selfhtml.

Lösung: Die fetch-API

Wo man früher noch mit XMLHttpRequest herumgeeiert hat, gibt es inzwischen weitaus elegantere Lösungen.
Einen einfachen Weg bietet die fetch-API. Sie ist in den meisten aktuellen Browsern in Form von window.fetch bereits nativ vorhanden.

Um sie in allen Browsern nutzen zu können, empfiehlt es sich, diese Funktion über einen Polyfill nachzurüsten.
z.B. von GitHub oder mit npm: npm install whatwg-fetch

Da noch nicht alle Browser Promises unterstützen, sollte auch hierfür ein Polyfill eingesetzt werden.
z.B. von GitHub oder mit npm: npm install promise-polyfill

Requests mit fetch haben einen einfachen Aufbau:

  1. Aufruf der Funktion fetch mit der URL des Webservices
  • then-Blöcke, die aufgerufen werden, wenn der Request erfolgreich war
  • Einen catch-Block, in dem festgelegt wird, was passiert, wenn der Request fehlschlug. Das kann beispielsweise eine Fehlermeldung oder ein Timeout sein.
fetch(url)
  .then(function(response) {
    /* Anwort hier verarbeiten */
  })
  .catch(function(error) {
    /* beim Error kommt man hier raus */
  })

Ein einfacher HTTP GET-Request könnte wie folgt aussehen:

fetch('json/first.json')
  .then(function(response) {
    // erfülltes Promise-Objekt in JSON umwandeln
    return response.json()

  }).then(function(json) {
    // Yes! Die Request war erfolgreich.
    console.log(json)

  }).catch(function(error) {
    /* Verdammt. Es gab einen Fehler beim Laden oder beim Parsen des JSON. */
    console.log('fetch failed: ', error)

  })

auf CodePen.io ausprobieren

Dem eifrigen Beobachter fällt schnell auf: Hier sind ja zwei then-Blocks verbaut!
Die then-Funktion kann beliebig oft hintereinander aufgerufen werden, so lange die im then-Block davor übergebene Funktion einen Wert zurückgibt.

Es kann auch ein weiterer Promise zurückgegeben werden:

fetch('json/first.json').then(function(response) {
  return response.json()

}).then(function(json) {
  // 1. Request war erfolgreich
  console.log(json)

  // URL für 2. Request
  var url = 'json/second.json'
  // 2. Request wird gestartet und weitergegeben
  return fetch(url)

}).then(function(response) {
  return response.json()

}).then(function(json) {
  // 2. Request war auch erfolgreich
  console.log(json)

}).catch(function(ex){
  // eine der Requests war nicht erfolgreich
  console.log("error")
})

auf CodePen.io ausprobieren

Dadurch wird der zweite Request erst abgefeuert, wenn der erste erfolgreich war. Wenn einer der beiden Requests erfolglos war, wird die catch-Funktion ausgeführt.

Obacht!
So wie die Verpackung Deines letzten Fastfood-Burgers sind auch Promises nur für die einmalige Benutzung geeignet. Ist ein Promise einmal settled (also entweder fulfilled oder rejected), ist es verbraucht und ein neuer Promise muss erstellt werden.

Noch mehr Obacht!
Bei den HTTP-Responses mit den Codes 404 (Not Found), 500 (Internal Server Error) oder 204(No Content), die generell als Misserfolg gewertet werden können, wird in den then-Block gesprungen. Generell solltest du nur Responses mit dem Code 200 annehmen.
Du kannst den Statuscode des Request im ersten then-Block mit response.status prüfen.
Alle HTTP-Statuscodes findest du bei Wikipedia.

Advanced

Genau so wie mit response.status kannst du auch weitere Parameter der Response wie z.B. den Statustext (response.statusText) oder den Content-Type (response.headers.get('Content-Type')) auslesen.

Du kannst der fetch-Funktion noch weitere Parameter für den HTTP-Request in einem options-Objekt übergeben übergeben: fetch( url, {headers: {'Content-Type': 'application/json'}} ).
Auch andere HTTP-Methoden als GET wie z.B. POST oder PUT sind möglich. Schau dir dazu am Besten die Dokumentation des fetch-Polyfills auf GitHub an.


Quellen

https://developers.google.com/web/fundamentals/primers/promises
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

Alle Beispiele von oben findest du auch auf GitHub.