Verwendung der Fetch API
DieFetch API bietet eine JavaScript-Schnittstelle, um HTTP-Anfragen zu stellen und die Antworten zu verarbeiten.
Fetch ist der moderne Ersatz fürXMLHttpRequest
: Im Gegensatz zuXMLHttpRequest
, das Rückruffunktionen verwendet, basiert Fetch auf Promises und ist mit modernen Web-Features wieService Worker undCross-Origin Resource Sharing (CORS) integriert.
Mit der Fetch API stellen Sie eine Anfrage, indem Siefetch()
aufrufen, welche als globale Funktion sowohl imwindow
- als auch imworker
-Kontext verfügbar ist. Sie übergeben ihr einRequest
-Objekt oder einen String mit der zu ladenden URL sowie ein optionales Argument zur Konfiguration der Anfrage.
Die Funktionfetch()
gibt einPromise
zurück, das mit einemResponse
-Objekt erfüllt wird, das die Antwort des Servers repräsentiert. Sie können dann den Anfragestatus überprüfen und den Inhalt des Antwortkörpers in verschiedenen Formaten, einschließlich Text und JSON, extrahieren, indem Sie die entsprechende Methode auf der Antwort aufrufen.
Hier ist eine minimale Funktion, diefetch()
verwendet, um einige JSON-Daten von einem Server abzurufen:
async function getData() { const url = "https://example.org/products.json"; try { const response = await fetch(url); if (!response.ok) { throw new Error(`Response status: ${response.status}`); } const json = await response.json(); console.log(json); } catch (error) { console.error(error.message); }}
Wir deklarieren einen String mit der URL und rufen dannfetch()
auf, wobei wir die URL ohne zusätzliche Optionen übergeben.
Die Funktionfetch()
wird bei einigen Fehlern das Promise verwerfen, jedoch nicht, wenn der Server mit einem Fehlerstatus wie404
antwortet: Daher überprüfen wir auch den Antwortstatus und werfen eine Ausnahme, wenn dieser nicht OK ist.
Andernfalls holen wir den Inhalt des Antwortkörpers alsJSON, indem wir die Methodejson()
vonResponse
aufrufen und einen ihrer Werte protokollieren. Beachten Sie, dassjson()
, genauso wiefetch()
selbst, asynchron ist, ebenso wie alle anderen Methoden, um auf den Inhalt des Antwortkörpers zuzugreifen.
Im Rest dieser Seite werden wir die verschiedenen Phasen dieses Prozesses detaillierter betrachten.
Eine Anfrage erstellen
Um eine Anfrage zu erstellen, rufen Siefetch()
auf und übergeben:
- eine Definition der abzurufenden Ressource. Dies kann eine der folgenden sein:
- ein String, der die URL enthält
- ein Objekt, wie eine Instanz von
URL
, das einenStringifier besitzt, der einen String mit der URL erzeugt - eine
Request
-Instanz
- optional ein Objekt mit Optionen zur Konfiguration der Anfrage.
In diesem Abschnitt betrachten wir einige der am häufigsten verwendeten Optionen. Um mehr über alle möglichen Optionen zu lesen, sehen Sie auf der Referenzseite vonfetch()
nach.
Die Methode festlegen
Standardmäßig führtfetch()
eineGET
-Anfrage aus, aber Sie können die Optionmethod
verwenden, um eine andereAnfragemethode zu verwenden:
const response = await fetch("https://example.org/post", { method: "POST", // …});
Wenn diemode
-Option aufno-cors
gesetzt ist, mussmethod
GET
,POST
oderHEAD
sein.
Einen Anfragekörper festlegen
Der Anfragekörper ist die Nutzlast der Anfrage: Es ist das, was der Client an den Server sendet. Sie können keinen Körper mitGET
-Anfragen einfügen, aber es ist nützlich für Anfragen, die Inhalte an den Server senden, wiePOST
- oderPUT
-Anfragen. Zum Beispiel, wenn Sie eine Datei an den Server hochladen möchten, könnten Sie einePOST
-Anfrage stellen und die Datei als Anfragekörper einfügen.
Um einen Anfragekörper festzulegen, übergeben Sie ihn alsbody
-Option:
const response = await fetch("https://example.org/post", { method: "POST", body: JSON.stringify({ username: "example" }), // …});
Sie können den Körper als Instanz einer der folgenden Typen angeben:
Andere Objekte werden durch ihretoString()
-Methode in Strings konvertiert. Zum Beispiel können Sie einURLSearchParams
-Objekt verwenden, um Formulardaten zu kodieren (sieheHeader setzen für mehr Informationen):
const response = await fetch("https://example.org/post", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, // Automatically converted to "username=example&password=password" body: new URLSearchParams({ username: "example", password: "password" }), // …});
Beachten Sie, dass wie bei Antwortkörpern auch Anfragkörper Streams sind, und das Stellen der Anfrage den Stream liest, so dass eine Anfrage mit einem Körper nicht zweimal gemacht werden kann:
const request = new Request("https://example.org/post", { method: "POST", body: JSON.stringify({ username: "example" }),});const response1 = await fetch(request);console.log(response1.status);// Will throw: "Body has already been consumed."const response2 = await fetch(request);console.log(response2.status);
Stattdessen müssten Sieeinen Klon erstellen der Anfrage, bevor sie gesendet wird:
const request1 = new Request("https://example.org/post", { method: "POST", body: JSON.stringify({ username: "example" }),});const request2 = request1.clone();const response1 = await fetch(request1);console.log(response1.status);const response2 = await fetch(request2);console.log(response2.status);
Weitere Informationen finden Sie unterGesperrte und gestörte Streams.
Header festlegen
Anfrageheader geben dem Server Informationen über die Anfrage: Beispielsweise sagt derContent-Type
-Header bei einerPOST
-Anfrage dem Server, in welchem Format der Körper der Anfrage vorliegt.
Um Anfrageheader festzulegen, weisen Sie sie derheaders
-Option zu.
Sie können hier ein Objektliteral übergeben, dasheader-name: header-value
-Eigenschaften enthält:
const response = await fetch("https://example.org/post", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username: "example" }), // …});
Alternativ können Sie einHeaders
-Objekt konstruieren, dem Objekt Header mitHeaders.append()
hinzufügen und dasHeaders
-Objekt dann derheaders
-Option zuweisen:
const myHeaders = new Headers();myHeaders.append("Content-Type", "application/json");const response = await fetch("https://example.org/post", { method: "POST", headers: myHeaders, body: JSON.stringify({ username: "example" }), // …});
Im Vergleich zur Verwendung einfacher Objekte bietet dasHeaders
-Objekt zusätzliche Eingabesanitierung. Beispielsweise normalisiert es Header-Namen in Kleinbuchstaben, entfernt führende und nachfolgende Leerzeichen aus Header-Werten und verhindert, dass bestimmte Header gesetzt werden. Viele Header werden automatisch vom Browser gesetzt und können nicht von einem Skript gesetzt werden: Diese werdenverbotene Anfrageheader genannt. Wenn diemode
-Option aufno-cors
gesetzt ist, wird das Set der erlaubten Header weiter eingeschränkt.
Daten in einer GET-Anfrage senden
GET
-Anfragen haben keinen Körper, aber Sie können dennoch Daten an den Server senden, indem Sie diese als Query-String an die URL anhängen. Dies ist eine übliche Methode, um Formulardaten an den Server zu senden. Sie können dies tun, indem SieURLSearchParams
verwenden, um die Daten zu kodieren, und sie dann an die URL anhängen:
const params = new URLSearchParams();params.append("username", "example");// GET request sent to https://example.org/login?username=exampleconst response = await fetch(`https://example.org/login?${params}`);
Cross-Origin-Anfragen durchführen
Ob eine Anfrage über Ursprung hinweg gemacht werden kann oder nicht, wird durch den Wert derRequestInit.mode
-Option bestimmt. Diese kann einen der drei Wertecors
,same-origin
oderno-cors
annehmen.
Für Fetch-Anfragen ist der Standardwert von
mode
cors
, was bedeutet, dass, wenn die Anfrage über Ursprung hinweg ist, sie denCross-Origin Resource Sharing (CORS)-Mechanismus verwendet. Das bedeutet, dass:- wenn die Anfrage eineeinfache Anfrage ist, wird die Anfrage immer gesendet, aber der Server muss mit dem korrekten
Access-Control-Allow-Origin
-Header antworten, andernfalls wird der Browser die Antwort nicht mit dem Anrufer teilen. - wenn die Anfrage keine einfache Anfrage ist, wird der Browser einevorabgefragte Anfrage senden, um zu überprüfen, ob der Server CORS versteht und die Anfrage erlaubt, und die tatsächliche Anfrage wird nicht gesendet, es sei denn, der Server antwortet auf die vorabgefragte Anfrage mit den entsprechenden CORS-Headern.
- wenn die Anfrage eineeinfache Anfrage ist, wird die Anfrage immer gesendet, aber der Server muss mit dem korrekten
Das Setzen von
mode
aufsame-origin
untersagt vollständig über Ursprung hinweggehende Anfragen.Das Setzen von
mode
aufno-cors
deaktiviert CORS für über Ursprung hinweggehende Anfragen. Dies beschränkt die setzbaren Header und beschränkt die Methoden auf GET, HEAD und POST. Die Antwort istopak, was bedeutet, dass ihre Header und der Körper für JavaScript nicht verfügbar sind. Meistens sollte eine Websiteno-cors
nicht verwenden: Die Hauptanwendung liegt in bestimmten Service-Worker-Anwendungsfällen.
Sehen Sie die Referenzdokumentation fürRequestInit.mode
für weitere Details.
Mit Anmeldeinformationen arbeiten
Anmeldeinformationen sind Cookies,TLS-Client-Zertifikate oder Authentifizierungsheader, die einen Benutzernamen und ein Passwort enthalten.
Um zu steuern, ob der Browser Anmeldeinformationen sendet oder nicht, sowie ob der Browser aufSet-Cookie
-Antwortheader achtet, setzen Sie diecredentials
-Option, die einen der folgenden drei Werte annehmen kann:
omit
: Niemals Anmeldeinformationen in der Anfrage senden oder in der Antwort einschließen.same-origin
(Standard): Nur Anmeldeinformationen für gleich ursprungsbezogene Anfragen senden und einschließen.include
: Immer Anmeldeinformationen einschließen, auch über Ursprung hinweg.
Beachten Sie, dass, wenn dasSameSite
-Attribut eines Cookies aufStrict
oderLax
gesetzt ist, das Cookie nicht über Sites hinweg gesendet wird, auch wenncredentials
aufinclude
gesetzt ist.
Das Einschließen von Anmeldeinformationen in Anfragen über Ursprung hinweg kann eine Site anfällig fürCSRF-Angriffe machen, daher muss der Server, selbst wenncredentials
aufinclude
gesetzt ist, auch ihrer Aufnahme zustimmen, indem er denAccess-Control-Allow-Credentials
-Header in seiner Antwort einfügt. Zusätzlich muss in diesem Fall der Server explizit den Ursprung des Clients imAccess-Control-Allow-Origin
-Antwortheader angeben (das heißt,*
ist nicht erlaubt).
Dies bedeutet, dass, wenncredentials
aufinclude
gesetzt ist und die Anfrage über Ursprung hinweggeht:
Wenn die Anfrage eineeinfache Anfrage ist, wird die Anfrage mit Anmeldeinformationen gesendet, aber der Server muss die
Access-Control-Allow-Credentials
- undAccess-Control-Allow-Origin
-Antwortheader setzen, oder der Browser gibt einen Netzwerkfehler an den Anrufer zurück. Wenn der Server die korrekten Header setzt, wird die Antwort, einschließlich der Anmeldeinformationen, dem Anrufer zugestellt.Wenn die Anfrage keine einfache Anfrage ist, wird der Browser einevorabgefragte Anfrage ohne Anmeldeinformationen senden, und der Server muss die
Access-Control-Allow-Credentials
- undAccess-Control-Allow-Origin
-Antwortheader setzen, oder der Browser gibt einen Netzwerkfehler an den Anrufer zurück. Wenn der Server die korrekten Header setzt, wird der Browser mit der tatsächlichen Anfrage fortfahren, einschließlich der Anmeldeinformationen, und wird die tatsächliche Antwort, einschließlich der Anmeldeinformationen, dem Anrufer zustellen.
EinRequest
-Objekt erstellen
Der KonstruktorRequest()
nimmt dieselben Argumente wiefetch()
selbst. Das bedeutet, dass anstatt Optionen anfetch()
zu übergeben, können Sie dieselben Optionen demRequest()
-Konstruktor übergeben und dann dieses Objekt anfetch()
übergeben.
Zum Beispiel können wir eine POST-Anfrage stellen, indem wir Optionen anfetch()
mit folgendem Code übergeben:
const myHeaders = new Headers();myHeaders.append("Content-Type", "application/json");const response = await fetch("https://example.org/post", { method: "POST", body: JSON.stringify({ username: "example" }), headers: myHeaders,});
Wir könnten dies jedoch umschreiben, um dieselben Argumente an denRequest()
-Konstruktor zu übergeben:
const myHeaders = new Headers();myHeaders.append("Content-Type", "application/json");const myRequest = new Request("https://example.org/post", { method: "POST", body: JSON.stringify({ username: "example" }), headers: myHeaders,});const response = await fetch(myRequest);
Das bedeutet auch, dass Sie eine Anfrage aus einer anderen Anfrage erstellen können, während Sie einige ihrer Eigenschaften mit dem zweiten Argument ändern:
async function post(request) { try { const response = await fetch(request); const result = await response.json(); console.log("Success:", result); } catch (error) { console.error("Error:", error); }}const request1 = new Request("https://example.org/post", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username: "example1" }),});const request2 = new Request(request1, { body: JSON.stringify({ username: "example2" }),});post(request1);post(request2);
Eine Anfrage abbrechen
Um eine Anfrage abbrechbar zu machen, erstellen Sie einenAbortController
und weisen dessenAbortSignal
dersignal
-Eigenschaft der Anfrage zu.
Um die Anfrage abzubrechen, rufen Sie dieabort()
-Methode des Controllers auf. Derfetch()
-Aufruf wird das Promise mit einerAbortError
-Ausnahme verwerfen.
const controller = new AbortController();const fetchButton = document.querySelector("#fetch");fetchButton.addEventListener("click", async () => { try { console.log("Starting fetch"); const response = await fetch("https://example.org/get", { signal: controller.signal, }); console.log(`Response: ${response.status}`); } catch (e) { console.error(`Error: ${e}`); }});const cancelButton = document.querySelector("#cancel");cancelButton.addEventListener("click", () => { controller.abort(); console.log("Canceled fetch");});
Wenn die Anfrage abgebrochen wird, nachdem derfetch()
-Aufruf erfüllt wurde, aber bevor der Antwortkörper gelesen wurde, dann wird der Versuch, den Antwortkörper zu lesen, mit einerAbortError
-Ausnahme abgelehnt.
async function get() { const controller = new AbortController(); const request = new Request("https://example.org/get", { signal: controller.signal, }); const response = await fetch(request); controller.abort(); // The next line will throw `AbortError` const text = await response.text(); console.log(text);}
Die Antwort verarbeiten
Sobald der Browser den Antwortstatus und die Header vom Server erhalten hat (und potenziell bevor der Antwortkörper selbst empfangen wurde), wird das vonfetch()
zurückgegebene Promise mit einemResponse
-Objekt erfüllt.
Überprüfen des Antwortstatus
Das vonfetch()
zurückgegebene Promise wird bei einigen Fehlern, wie einem Netzwerkfehler oder einem schlechten Schema, abgelehnt. Wenn jedoch der Server mit einem Fehler wie404
antwortet, wirdfetch()
mit einerResponse
erfüllt, daher müssen wir den Status überprüfen, bevor wir den Antwortkörper lesen können.
Die EigenschaftResponse.status
gibt uns den numerischen Statuscode an, und die EigenschaftResponse.ok
gibttrue
zurück, wenn der Status im200-Bereich liegt.
Ein gebräuchliches Muster ist, den Wert vonok
zu überprüfen und eine Ausnahme zu werfen, wenn erfalse
ist:
async function getData() { const url = "https://example.org/products.json"; try { const response = await fetch(url); if (!response.ok) { throw new Error(`Response status: ${response.status}`); } // … } catch (error) { console.error(error.message); }}
Überprüfen des Antworttyps
Antworten haben einetype
-Eigenschaft, die einer der folgenden sein kann:
basic
: Die Anfrage war eine gleiche Ursprungsanfrage.cors
: Die Anfrage war eine über Ursprung hinausgehende CORS-Anfrage.opaque
: Die Anfrage war eine über Ursprung hinausragende einfache Anfrage, die imno-cors
-Modus gemacht wurde.opaqueredirect
: Die Anfrage setzte dieredirect
-Option aufmanual
, und der Server gab einenUmleitungsstatus zurück.
Der Typ bestimmt den möglichen Inhalt der Antwort wie folgt:
Basic-Antworten schließen Antwortheader aus der Listeverbotene Antwortheadernamen aus.
CORS-Antworten enthalten nur Antwortheader aus derCORS-sicherheitsfreigegebenen Antwortheader-Liste.
Opake Antworten und opake Umleitungsantworten haben einen
status
von0
, eine leere Liste von Headern und einennull
-Körper.
Überprüfen von Headern
Genau wie die Anfrage verfügt auch die Antwort über eineheaders
-Eigenschaft, das einHeaders
-Objekt ist, und dies enthält alle Antwortheader, die Skripten zur Verfügung stehen, unter Berücksichtigung der Ausschlüsse basierend auf dem Antworttyp.
Ein häufiger Anwendungsfall hierfür ist, den Inhaltstyp zu überprüfen, bevor versucht wird, den Körper zu lesen:
async function fetchJSON(request) { try { const response = await fetch(request); const contentType = response.headers.get("content-type"); if (!contentType || !contentType.includes("application/json")) { throw new TypeError("Oops, we haven't got JSON!"); } // Otherwise, we can read the body as JSON } catch (error) { console.error("Error:", error); }}
Den Antwortkörper lesen
DasResponse
-Interface bietet eine Reihe von Methoden, um den gesamten Inhalt des Körpers in einer Vielzahl unterschiedlicher Formate abzurufen:
Dies sind alles asynchrone Methoden, die einPromise
zurückgeben, das mit dem Körperinhalt erfüllt wird.
In diesem Beispiel laden wir ein Bild und lesen es alsBlob
, das wir dann verwenden können, um eine Objekt-URL zu erstellen:
const image = document.querySelector("img");const url = "flowers.jpg";async function setImage() { try { const response = await fetch(url); if (!response.ok) { throw new Error(`Response status: ${response.status}`); } const blob = await response.blob(); const objectURL = URL.createObjectURL(blob); image.src = objectURL; } catch (e) { console.error(e); }}
Die Methode wird eine Ausnahme werfen, wenn der Antwortkörper nicht im entsprechenden Format vorliegt: beispielsweise, wenn Siejson()
auf eine Antwort aufrufen, die nicht als JSON geparst werden kann.
Den Antwortkörper streamen
Anfrage- und Antwortkörper sind tatsächlichReadableStream
-Objekte, und wann immer Sie sie lesen, streamen Sie den Inhalt. Dies ist gut für die Speichereffizienz, da der Browser den gesamten Antwortinhalt nicht vollständig im Speicher puffern muss, bevor es vom Anrufer mit einer Methode wiejson()
abgerufen wird.
Das bedeutet auch, dass der Anrufer den Inhalt schrittweise während des Empfangs verarbeiten kann.
Zum Beispiel, betrachten Sie eineGET
-Anfrage, die eine große Textdatei lädt und auf irgendeine Weise verarbeitet oder sie dem Benutzer anzeigt:
const url = "https://www.example.org/a-large-file.txt";async function fetchText(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`Response status: ${response.status}`); } const text = await response.text(); console.log(text); } catch (e) { console.error(e); }}
Wenn wirResponse.text()
verwenden, wie oben, müssen wir warten, bis die gesamte Datei empfangen wurde, bevor wir irgendetwas davon verarbeiten können.
Wenn wir stattdessen die Antwort streamen, können wir Teile des Körpers verarbeiten, während sie vom Netzwerk empfangen werden:
const url = "https://www.example.org/a-large-file.txt";async function fetchTextAsStream(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`Response status: ${response.status}`); } const stream = response.body.pipeThrough(new TextDecoderStream()); for await (const value of stream) { console.log(value); } } catch (e) { console.error(e); }}
In diesem Beispieliterieren wir asynchron über den Stream und verarbeiten jeden Chunk, sobald er ankommt.
Beachten Sie, dass wenn Sie den Körper direkt so zugreifen, erhalten Sie die rohen Bytes der Antwort und müssen sie selbst transformieren. In diesem Fall rufen wirReadableStream.pipeThrough()
auf, um die Antwort durch einenTextDecoderStream
zu leiten, der die UTF-8-codierten Körpers als Text dekodiert.
Eine Textdatei zeilenweise verarbeiten
Im folgenden Beispiel laden wir eine Textressource und verarbeiten sie zeilenweise, indem wir mit einem regulären Ausdruck nach Zeilenumbrüchen suchen. Zur Vereinfachung nehmen wir an, dass der Text UTF-8 ist, und behandeln keine Fehler bei Fetch:
async function* makeTextFileLineIterator(fileURL) { const response = await fetch(fileURL); const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); let { value: chunk = "", done: readerDone } = await reader.read(); const newline = /\r?\n/g; let startIndex = 0; while (true) { const result = newline.exec(chunk); if (!result) { if (readerDone) break; const remainder = chunk.slice(startIndex); ({ value: chunk, done: readerDone } = await reader.read()); chunk = remainder + (chunk || ""); startIndex = newline.lastIndex = 0; continue; } yield chunk.substring(startIndex, result.index); startIndex = newline.lastIndex; } if (startIndex < chunk.length) { // Last line didn't end in a newline char yield chunk.substring(startIndex); }}async function run(urlOfFile) { for await (const line of makeTextFileLineIterator(urlOfFile)) { processLine(line); }}function processLine(line) { console.log(line);}run("https://www.example.org/a-large-file.txt");
Gesperrte und gestörte Streams
Die Konsequenzen von Anfragen- und Antwortkörpern als Streams sind, dass:
- Wenn ein Leser an einen Stream mit
ReadableStream.getReader()
angehängt wurde, ist der Streamgesperrt, und nichts anderes kann den Stream lesen. - Wenn ein beliebiger Inhalt aus dem Stream gelesen wurde, ist der Streamgestört, und nichts anderes kann aus dem Stream lesen.
Das bedeutet, dass es nicht möglich ist, den gleichen Antwort- (oder Anfrage-)Körper mehrmals zu lesen:
async function getData() { const url = "https://example.org/products.json"; try { const response = await fetch(url); if (!response.ok) { throw new Error(`Response status: ${response.status}`); } const json1 = await response.json(); const json2 = await response.json(); // will throw } catch (error) { console.error(error.message); }}
Wenn Sie den Körper mehrmals lesen müssen, müssen SieResponse.clone()
aufrufen, bevor Sie den Körper lesen:
async function getData() { const url = "https://example.org/products.json"; try { const response1 = await fetch(url); if (!response1.ok) { throw new Error(`Response status: ${response1.status}`); } const response2 = response1.clone(); const json1 = await response1.json(); const json2 = await response2.json(); } catch (error) { console.error(error.message); }}
Dies ist ein übliches Muster beimImplementieren eines Offline-Caches mit Service-Workern. Der Service Worker möchte die Antwort an die App zurückgeben, sie aber auch cachen. Also klont er die Antwort, gibt das Original zurück und speichert den Klon:
async function cacheFirst(request) { const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; } try { const networkResponse = await fetch(request); if (networkResponse.ok) { const cache = await caches.open("MyCache_1"); cache.put(request, networkResponse.clone()); } return networkResponse; } catch (error) { return Response.error(); }}self.addEventListener("fetch", (event) => { if (precachedResources.includes(url.pathname)) { event.respondWith(cacheFirst(event.request)); }});
Siehe auch
MDN-Feedback-Box
Diese Seite wurde automatisch aus dem Englischen übersetzt.