Using Fetch

Avatar of Zell Liew
Zell Liew am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Wenn wir mit JavaScript Informationen senden oder abrufen, initiieren wir etwas, das als Ajax-Aufruf bekannt ist. Ajax ist eine Technik zum Senden und Abrufen von Informationen im Hintergrund, ohne dass die Seite neu geladen werden muss. Es ermöglicht Browsern, Informationen zu senden und abzurufen und dann mit dem, was sie zurückbekommen, Dinge zu tun, wie z. B. HTML auf der Seite hinzuzufügen oder zu ändern.

Werfen wir einen Blick auf die Geschichte und bringen uns dann auf den neuesten Stand.

Suchen Sie nur nach dem grundlegenden Fetch-Snippet? Hier ist es.
fetch(URL)
  .then(response => response.json())
  .then(data => {
    console.log(data)
  });

Noch ein Hinweis: Wir werden die ES6-Syntax für alle Demos in diesem Artikel verwenden.

Vor einigen Jahren war der einfachste Weg, einen Ajax-Aufruf zu initiieren, die Verwendung der ajax-Methode von jQuery

$.ajax('some-url', {
  success: (data) => { /* do something with the data */ },
  error: (err) => { /* do something when an error happens */}
});

Wir konnten Ajax ohne jQuery machen, aber wir mussten ein XMLHttpRequest schreiben, was ziemlich kompliziert ist.

Glücklicherweise haben sich die Browser heutzutage so sehr verbessert, dass sie die Fetch API unterstützen, eine moderne Möglichkeit für Ajax ohne Hilfsbibliotheken wie jQuery oder Axios. In diesem Artikel zeige ich Ihnen, wie Sie Fetch verwenden, um sowohl Erfolge als auch Fehler zu behandeln.

Unterstützung für Fetch

Lassen Sie uns zuerst die Unterstützung klären.

Diese Browser-Unterstützungsdaten stammen von Caniuse, wo Sie weitere Details finden. Eine Zahl gibt an, dass der Browser die Funktion ab dieser Version unterstützt.

Desktop

ChromeFirefoxIEEdgeSafari
4239Nein1410.1

Mobil / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712710.3

Die Unterstützung für Fetch ist ziemlich gut! Alle gängigen Browser (mit Ausnahme von Opera Mini und alten IE) unterstützen ihn nativ, was bedeutet, dass Sie ihn sicher in Ihren Projekten verwenden können. Wenn Sie Unterstützung dort benötigen, wo er nicht nativ unterstützt wird, können Sie sich immer auf dieses praktische Polyfill verlassen.

Daten mit Fetch abrufen

Das Abrufen von Daten mit Fetch ist einfach. Sie müssen Fetch nur die Ressource angeben, die Sie abrufen möchten (so meta!).

Nehmen wir an, wir versuchen, eine Liste von Chris' Repositories auf Github abzurufen. Laut Github's API müssen wir eine GET-Anfrage an api.github.com/users/chriscoyier/repos stellen.

Dies wäre die Fetch-Anfrage

fetch('https://api.github.com/users/chriscoyier/repos');

So einfach! Was kommt als Nächstes?

Fetch gibt ein Promise zurück, was eine Möglichkeit ist, asynchrone Operationen ohne Callbacks zu behandeln.

Um etwas zu tun, nachdem die Ressource abgerufen wurde, schreiben Sie es in einem .then-Aufruf

fetch('https://api.github.com/users/chriscoyier/repos')
  .then(response => {/* do something */})

Wenn dies Ihre erste Begegnung mit Fetch ist, werden Sie wahrscheinlich von der response überrascht sein, die Fetch zurückgibt. Wenn Sie die Antwort mit console.log ausgeben, erhalten Sie folgende Informationen

{
  body: ReadableStream
  bodyUsed: false
  headers: Headers
  ok : true
  redirected : false
  status : 200
  statusText : "OK"
  type : "cors"
  url : "http://some-website.com/some-url"
  __proto__ : Response
}

Hier sehen Sie, dass Fetch eine Antwort zurückgibt, die Ihnen den Status der Anfrage mitteilt. Wir sehen, dass die Anfrage erfolgreich ist (ok ist true und status ist 200), aber eine Liste von Chris' Repos ist nirgends zu finden!

Es stellt sich heraus, dass das, was wir von Github angefordert haben, im body als lesbarer Stream versteckt ist. Wir müssen eine geeignete Methode aufrufen, um diesen lesbaren Stream in Daten umzuwandeln, die wir verarbeiten können.

Da wir mit GitHub arbeiten, wissen wir, dass die Antwort JSON ist. Wir können response.json aufrufen, um die Daten umzuwandeln.

Es gibt andere Methoden, um mit verschiedenen Antworttypen umzugehen. Wenn Sie eine XML-Datei anfordern, sollten Sie response.text aufrufen. Wenn Sie ein Bild anfordern, rufen Sie response.blob auf.

Alle diese Konvertierungsmethoden (response.json usw.) geben ein weiteres Promise zurück, sodass wir die gewünschten Daten mit einem weiteren .then-Aufruf erhalten können.

fetch('https://api.github.com/users/chriscoyier/repos')
  .then(response => response.json())
  .then(data => {
    // Here's a list of repos!
    console.log(data)
  });

Puh! Das ist alles, was Sie tun müssen, um Daten mit Fetch abzurufen! Kurz und bündig, nicht wahr? :)

Als Nächstes sehen wir uns an, wie Daten mit Fetch gesendet werden.

Daten mit Fetch senden

Das Senden von Daten mit Fetch ist ebenfalls ziemlich einfach. Sie müssen Ihre Fetch-Anfrage nur mit drei Optionen konfigurieren.

fetch('some-url', options);

Die erste Option, die Sie festlegen müssen, ist Ihre Anfragemethode auf post, put oder del. Fetch setzt die method automatisch auf get, wenn Sie sie weglassen, weshalb der Abruf einer Ressource weniger Schritte erfordert.

Die zweite Option ist das Festlegen Ihrer Header. Da wir heutzutage hauptsächlich JSON-Daten senden, müssen wir Content-Type auf application/json setzen.

Die dritte Option ist das Festlegen eines Bodys, der JSON-Inhalt enthält. Da JSON-Inhalt erforderlich ist, müssen Sie oft JSON.stringify aufrufen, wenn Sie den body festlegen.

In der Praxis sieht eine post-Anfrage mit diesen drei Optionen so aus

let content = {some: 'content'};

// The actual fetch request
fetch('some-url', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(content)
})
// .then()...

Für die scharfen Augen: Sie werden einige Boilerplate-Codes für jede post-, put- oder del-Anfrage bemerken. Idealerweise können wir unsere Header wiederverwenden und den Inhalt vor dem Senden mit JSON.stringify aufrufen, da wir bereits wissen, dass wir JSON-Daten senden.

Aber auch mit dem Boilerplate-Code ist Fetch immer noch ziemlich gut für das Senden jeder Anfrage.

Die Fehlerbehandlung mit Fetch ist jedoch nicht so unkompliziert wie die Behandlung von Erfolgsmeldungen. Sie werden gleich sehen, warum.

Fehlerbehandlung mit Fetch

Obwohl wir immer auf erfolgreiche Ajax-Anfragen hoffen, können sie fehlschlagen. Es gibt viele Gründe, warum Anfragen fehlschlagen können, darunter, aber nicht beschränkt auf:

  1. Sie haben versucht, eine nicht existierende Ressource abzurufen.
  2. Sie sind nicht berechtigt, die Ressource abzurufen.
  3. Sie haben einige Argumente falsch eingegeben
  4. Der Server wirft einen Fehler.
  5. Der Server hat ein Timeout.
  6. Der Server ist abgestürzt.
  7. Die API hat sich geändert.

Die Dinge werden nicht schön, wenn Ihre Anfrage fehlschlägt. Stellen Sie sich ein Szenario vor, in dem Sie etwas online kaufen wollten. Ein Fehler ist aufgetreten, aber er wurde von den Leuten, die die Website programmiert haben, nicht behandelt. Infolgedessen bewegt sich nach dem Klicken auf "Kaufen" nichts mehr. Die Seite hängt einfach dort ... Sie haben keine Ahnung, ob etwas passiert ist. Wurde Ihre Karte belastet? 😱.

Lassen Sie uns nun versuchen, einen nicht existierenden Fehler abzurufen und zu lernen, wie man Fehler mit Fetch behandelt. Nehmen wir für dieses Beispiel an, wir haben chriscoyier als chrissycoyier falsch geschrieben.

// Fetching chrissycoyier's repos instead of chriscoyier's repos
fetch('https://api.github.com/users/chrissycoyier/repos')

Wir wissen bereits, dass wir einen Fehler bekommen sollten, da es kein chrissycoyier auf Github gibt. Um Fehler in Promises zu behandeln, verwenden wir einen catch-Aufruf.

Angesichts dessen, was wir jetzt wissen, werden Sie wahrscheinlich diesen Code entwickeln

fetch('https://api.github.com/users/chrissycoyier/repos')
  .then(response => response.json())
  .then(data => console.log('data is', data))
  .catch(error => console.log('error is', error));

Führen Sie Ihre Fetch-Anfrage aus. Das erhalten Sie

Fetch ist fehlgeschlagen, aber der Code, der ausgeführt wird, ist das zweite `.then` anstelle von `.catch`

Warum wurde unser zweiter .then-Aufruf ausgeführt? Sind Promises nicht dazu gedacht, Fehler mit .catch zu behandeln? Schrecklich! 😱😱😱

Wenn Sie die Antwort mit console.log ausgeben, sehen Sie leicht unterschiedliche Werte

{
  body: ReadableStream
  bodyUsed: true
  headers: Headers
  ok: false // Response is not ok
  redirected: false
  status: 404 // HTTP status is 404.
  statusText: "Not Found" // Request not found
  type: "cors"
  url: "https://api.github.com/users/chrissycoyier/repos"
}

Die meisten Antworten bleiben gleich, außer ok, status und statusText. Wie erwartet haben wir chrissycoyier nicht auf Github gefunden.

Diese Antwort besagt, dass Fetch es egal ist, ob Ihre AJAX-Anfrage erfolgreich war. Es geht nur darum, eine Anfrage zu senden und eine Antwort vom Server zu erhalten, was bedeutet, dass wir einen Fehler auslösen müssen, wenn die Anfrage fehlschlägt.

Daher muss der anfängliche then-Aufruf neu geschrieben werden, so dass er nur response.json aufruft, wenn die Anfrage erfolgreich war. Der einfachste Weg, dies zu tun, ist zu prüfen, ob die response ok ist.

fetch('some-url')
  .then(response => {
    if (response.ok) {
      return response.json()
    } else {
      // Find some way to get to execute .catch()
    }
  });

Sobald wir wissen, dass die Anfrage nicht erfolgreich war, können wir entweder einen Fehler throwen oder ein Promise rejecten, um den catch-Aufruf zu aktivieren.

// throwing an Error
else {
  throw new Error('something went wrong!')
}

// rejecting a Promise
else {
  return Promise.reject('something went wrong!')
}

Wählen Sie eine der beiden Optionen, da beide den .catch-Aufruf aktivieren.

Hier wähle ich Promise.reject, weil es einfacher zu implementieren ist. Fehler sind auch cool, aber sie sind schwieriger zu implementieren, und der einzige Vorteil eines Fehlers ist ein Stack-Trace, der bei einer Fetch-Anfrage ohnehin nicht vorhanden wäre.

Der Code sieht also bisher so aus

fetch('https://api.github.com/users/chrissycoyier/repos')
  .then(response => {
    if (response.ok) {
      return response.json()
    } else {
      return Promise.reject('something went wrong!')
    }
  })
  .then(data => console.log('data is', data))
  .catch(error => console.log('error is', error));
Fehlgeschlagene Anfrage, aber der Fehler wird korrekt an catch übergeben

Das ist großartig. Wir kommen voran, da wir jetzt eine Möglichkeit haben, Fehler zu behandeln.

Aber das Ablehnen des Promises (oder das Auslösen eines Fehlers) mit einer generischen Meldung reicht nicht aus. Wir werden nicht wissen, was schief gelaufen ist. Ich bin mir ziemlich sicher, dass Sie nicht am Empfängerende einer solchen Fehlermeldung sein möchten...

Ja... ich verstehe, dass etwas schief gelaufen ist... aber was genau? 🙁

Was ist schief gelaufen? Hat der Server ein Timeout gehabt? Wurde meine Verbindung unterbrochen? Es gibt keinen Weg, das herauszufinden! Wir brauchen eine Möglichkeit, festzustellen, was mit der Anfrage schief gelaufen ist, damit wir sie angemessen behandeln können.

Schauen wir uns die Antwort noch einmal an und sehen, was wir tun können

{
  body: ReadableStream
  bodyUsed: true
  headers: Headers
  ok: false // Response is not ok
  redirected: false
  status: 404 // HTTP status is 404.
  statusText: "Not Found" // Request not found
  type: "cors"
  url: "https://api.github.com/users/chrissycoyier/repos"
}

Okay, großartig. In diesem Fall wissen wir, dass die Ressource nicht existiert. Wir können einen 404-Status oder den Status-Text Not Found zurückgeben und wir wissen, was wir damit tun sollen.

Um status und statusText in den .catch-Aufruf zu bekommen, können wir ein JavaScript-Objekt ablehnen.

fetch('some-url')
  .then(response => {
    if (response.ok) {
      return response.json()
    } else {
      return Promise.reject({
        status: response.status,
        statusText: response.statusText
      })
    }
  })
  .catch(error => {
    if (error.status === 404) {
      // do something about 404
    }
  })

Jetzt kommen wir wieder voran! Juhu! 😄.

Machen wir es besser! 😏.

Die obige Fehlerbehandlungsmethode ist für bestimmte HTTP-Statuscodes ausreichend, die keine weitere Erklärung erfordern, wie z. B.:

  • 401: Nicht autorisiert
  • 404: Nicht gefunden
  • 408: Verbindungs-Timeout

Aber es ist nicht gut genug für diesen speziellen badass

  • 400: Schlechte Anfrage.

Was eine schlechte Anfrage ausmacht, kann eine ganze Reihe von Dingen sein! Zum Beispiel gibt Stripe 400 zurück, wenn bei der Anfrage ein erforderlicher Parameter fehlt.

Stripe erklärt, dass es einen 400-Fehler zurückgibt, wenn bei der Anfrage ein erforderliches Feld fehlt

Es reicht nicht aus, unserem .catch-Statement nur mitzuteilen, dass es eine schlechte Anfrage gab. Wir brauchen mehr Informationen, um festzustellen, was fehlt. Hat Ihr Benutzer seinen Vornamen vergessen? E-Mail? Oder vielleicht seine Kreditkarteninformationen? Das werden wir nicht wissen!

Idealerweise würde Ihr Server in solchen Fällen ein Objekt zurückgeben, das Ihnen mitteilt, was passiert ist, zusammen mit der fehlgeschlagenen Anfrage. Wenn Sie Node und Express verwenden, könnte eine solche Antwort so aussehen.

res.status(400).send({
  err: 'no first name'
})

Hier können wir kein Promise im anfänglichen .then-Aufruf ablehnen, da das Fehlerobjekt vom Server erst nach response.json gelesen werden kann.

Die Lösung ist, ein Promise zurückzugeben, das zwei then-Aufrufe enthält. So können wir zuerst lesen, was in response.json steht, und dann entscheiden, was wir damit tun.

So sieht der Code aus

fetch('some-error')
  .then(handleResponse)

function handleResponse(response) {
  return response.json()
    .then(json => {
      if (response.ok) {
        return json
      } else {
        return Promise.reject(json)
      }
    })
}

Brechen wir den Code auf. Zuerst rufen wir response.json auf, um die JSON-Daten zu lesen, die der Server gesendet hat. Da response.json ein Promise zurückgibt, können wir sofort .then aufrufen, um zu lesen, was darin enthalten ist.

Wir wollen dieses zweite .then innerhalb des ersten .then aufrufen, da wir immer noch auf response.ok zugreifen müssen, um zu bestimmen, ob die Antwort erfolgreich war.

Wenn Sie den Status und den Status-Text zusammen mit dem JSON in .catch senden möchten, können Sie sie mit Object.assign() zu einem Objekt zusammenfassen.

let error = Object.assign({}, json, {
  status: response.status,
  statusText: response.statusText
})
return Promise.reject(error)

Mit dieser neuen Funktion handleResponse können Sie Ihren Code so schreiben, und Ihre Daten werden automatisch an .then und .catch übergeben.

fetch('some-url')
  .then(handleResponse)
  .then(data => console.log(data))
  .catch(error => console.log(error))

Leider sind wir mit der Behandlung der Antwort noch nicht fertig :(.

Andere Antworttypen behandeln

Bisher haben wir uns nur mit der Behandlung von JSON-Antworten mit Fetch befasst. Das löst bereits 90 % der Anwendungsfälle, da APIs heutzutage JSON zurückgeben.

Was ist mit den anderen 10 %?

Nehmen wir an, Sie erhalten eine XML-Antwort mit dem obigen Code. Sofort erhalten Sie in Ihrer Catch-Anweisung einen Fehler mit der Meldung:

Das Parsen eines ungültigen JSONs erzeugt einen Syntaxfehler

Das liegt daran, dass XML kein JSON ist. Wir können response.json einfach nicht zurückgeben. Stattdessen müssen wir response.text zurückgeben. Dazu müssen wir den Inhaltstyp überprüfen, indem wir auf die Antwort-Header zugreifen.

.then(response => {
  let contentType = response.headers.get('content-type')

  if (contentType.includes('application/json')) {
    return response.json()
    // ...
  }

  else if (contentType.includes('text/html')) {
    return response.text()
    // ...
  }

  else {
    // Handle other responses accordingly...
  }
});

Fragen Sie sich, warum Sie jemals eine XML-Antwort erhalten würden?

Nun, ich bin darauf gestoßen, als ich versuchte, ExpressJWT zur Authentifizierung auf meinem Server zu verwenden. Damals wusste ich nicht, dass man JSON als Antwort senden kann, also beließ ich es bei der Standardeinstellung, XML. Dies ist nur eine von vielen unerwarteten Möglichkeiten, auf die Sie stoßen werden. Möchten Sie eine weitere? Versuchen Sie, some-url abzurufen :)

Wie auch immer, hier ist der gesamte Code, den wir bisher behandelt haben

fetch('some-url')
  .then(handleResponse)
  .then(data => console.log(data))
  .catch(error => console.log(error))

function handleResponse (response) {
  let contentType = response.headers.get('content-type')
  if (contentType.includes('application/json')) {
    return handleJSONResponse(response)
  } else if (contentType.includes('text/html')) {
    return handleTextResponse(response)
  } else {
    // Other response types as necessary. I haven't found a need for them yet though.
    throw new Error(`Sorry, content-type ${contentType} not supported`)
  }
}

function handleJSONResponse (response) {
  return response.json()
    .then(json => {
      if (response.ok) {
        return json
      } else {
        return Promise.reject(Object.assign({}, json, {
          status: response.status,
          statusText: response.statusText
        }))
      }
    })
}
function handleTextResponse (response) {
  return response.text()
    .then(text => {
      if (response.ok) {
        return text
      } else {
        return Promise.reject({
          status: response.status,
          statusText: response.statusText,
          err: text
        })
      }
    })
}

Es ist viel Code zum Schreiben/Kopieren und Einfügen, wenn Sie Fetch verwenden. Da ich Fetch in meinen Projekten intensiv nutze, habe ich eine Bibliothek um Fetch herum erstellt, die genau das tut, was ich in diesem Artikel beschrieben habe (plus ein bisschen mehr).

zlFetch vorstellen

zlFetch ist eine Bibliothek, die die Funktion handleResponse abstrahiert, sodass Sie überspringen und sowohl Ihre Daten als auch Fehler behandeln können, ohne sich um die Antwort kümmern zu müssen.

Ein typisches zlFetch sieht so aus

zlFetch('some-url', options)
  .then(data => console.log(data))
  .catch(error => console.log(error));

Um zlFetch zu verwenden, müssen Sie es zuerst installieren.

npm install zl-fetch --save

Dann importieren Sie es in Ihren Code. (Beachten Sie default, wenn Sie keine ES6-Imports verwenden.) Wenn Sie ein Polyfill benötigen, stellen Sie sicher, dass Sie es importieren, bevor Sie zlFetch hinzufügen.

// Polyfills (if needed)
require('isomorphic-fetch') // or whatwg-fetch or node-fetch if you prefer

// ES6 Imports
import zlFetch from 'zl-fetch';

// CommonJS Imports
const zlFetch = require('zl-fetch');

zlFetch tut mehr, als die Notwendigkeit zu entfernen, eine Fetch-Antwort zu verarbeiten. Es hilft Ihnen auch, JSON-Daten zu senden, ohne Header schreiben oder Ihren Body in JSON konvertieren zu müssen.

Die Funktionen darunter tun dasselbe. zlFetch fügt einen Content-Type hinzu und konvertiert Ihren Inhalt im Hintergrund in JSON.

let content = {some: 'content'}

// Post request with fetch
fetch('some-url', {
  method: 'post',
  headers: {'Content-Type': 'application/json'}
  body: JSON.stringify(content)
});

// Post request with zlFetch
zlFetch('some-url', {
  method: 'post',
  body: content
});

zlFetch erleichtert auch die Authentifizierung mit JSON Web Tokens.

Die Standardpraxis für die Authentifizierung besteht darin, einen Authorization-Schlüssel in die Header einzufügen. Der Inhalt dieses Authorization-Schlüssels wird auf Bearer your-token-here gesetzt. zlFetch hilft bei der Erstellung dieses Feldes, wenn Sie eine token-Option hinzufügen.

Die folgenden beiden Codeausschnitte sind also äquivalent.

let token = 'someToken'
zlFetch('some-url', {
  headers: {
    Authorization: `Bearer ${token}`
  }
});

// Authentication with JSON Web Tokens with zlFetch
zlFetch('some-url', {token});

Das ist alles, was zlFetch tut. Es ist nur eine praktische Wrapper-Funktion, die Ihnen hilft, weniger Code zu schreiben, wenn Sie Fetch verwenden. Schauen Sie sich zlFetch an, wenn Sie es interessant finden. Andernfalls können Sie gerne Ihre eigene Lösung entwickeln!

Hier ist ein Pen zum Herumspielen mit zlFetch

Zusammenfassung

Fetch ist eine erstaunliche Technologie, die das Senden und Empfangen von Daten zum Kinderspiel macht. Wir müssen keine XHR-Anfragen mehr manuell schreiben oder uns auf größere Bibliotheken wie jQuery verlassen.

Obwohl Fetch großartig ist, ist die Fehlerbehandlung mit Fetch nicht unkompliziert. Bevor Sie Fehler ordnungsgemäß behandeln können, benötigen Sie eine ganze Menge Boilerplate-Code, um Informationen an Ihren .catch-Aufruf zu übergeben.

Mit zlFetch (und den in diesem Artikel vorgestellten Informationen) gibt es keinen Grund mehr, warum wir Fehler nicht mehr richtig behandeln können. Gehen Sie raus und bringen Sie auch Spaß in Ihre Fehlermeldungen :)


Übrigens, wenn Ihnen dieser Beitrag gefallen hat, könnten Ihnen auch andere Front-End-bezogene Artikel gefallen, die ich auf meinem Blog schreibe. Schauen Sie gerne vorbei und stellen Sie alle Fragen, die Sie haben. Ich werde Ihnen so schnell wie möglich antworten.