Netzwerk-Anfragen mit JavaScript
Eine weitere sehr häufige Aufgabe in modernen Websites und Anwendungen ist das Stellen von Netzwerk-Anfragen, um einzelne Datenobjekte vom Server abzurufen, um Bereiche einer Webseite zu aktualisieren, ohne eine komplett neue Seite laden zu müssen. Dieses scheinbar kleine Detail hat große Auswirkungen auf die Leistung und das Verhalten von Websites gehabt. In diesem Artikel werden wir das Konzept erklären und Technologien betrachten, die dies ermöglichen, insbesondere die Fetch API.
Voraussetzungen: | Ein Verständnis von HTML und den Grundlagen von CSS, sowie Vertrautheit mit den JavaScript-Grundlagen, die in vorherigen Lektionen behandelt wurden. |
---|---|
Lernziele: |
|
Was ist hier das Problem?
Eine Webseite besteht aus einer HTML-Seite und (gewöhnlich) verschiedenen anderen Dateien wie Stylesheets, Skripten und Bildern. Das grundlegende Modell des Seitenladens im Web ist, dass Ihr Browser eine oder mehrere HTTP-Anfragen an den Server für die zum Anzeigen der Seite benötigten Dateien stellt, und der Server mit den angeforderten Dateien antwortet. Wenn Sie eine andere Seite besuchen, fordert der Browser die neuen Dateien an, und der Server liefert sie.
Dieses Modell funktioniert für viele Websites perfekt. Aber betrachten Sie eine Website, die sehr datengetrieben ist. Zum Beispiel eine Bibliothekswebsite wie die Vancouver Public Library. Solch eine Seite könnte als Benutzeroberfläche für eine Datenbank betrachtet werden. Möglicherweise können Sie nach einem bestimmten Buchgenre suchen oder Empfehlungen für Bücher anzeigen lassen, die Ihnen auf Basis von zuvor ausgeliehenen Büchern gefallen könnten. Wenn Sie dies tun, muss die Seite mit der neuen Buchauswahl aktualisiert werden. Aber beachten Sie, dass der größte Teil des Seiteninhalts – einschließlich Elemente wie Seitenkopf, Seitenleiste und Fußzeile – gleich bleibt.
Das Problem mit dem traditionellen Modell in diesem Fall ist, dass wir die gesamte Seite abrufen und laden müssten, selbst wenn wir nur einen Teil davon aktualisieren müssen. Dies ist ineffizient und kann zu einer schlechten Benutzererfahrung führen.
Stattdessen verwenden viele Websites JavaScript-APIs, um Daten vom Server anzufordern und den Seiteninhalt ohne Seitenladung zu aktualisieren. Wenn der Nutzer also nach einem neuen Produkt sucht, fordert der Browser nur die Daten an, die benötigt werden, um die Seite zu aktualisieren – zum Beispiel die neuen anzuzeigenden Bücher.
Die Haupt-API hier ist die Fetch API. Sie ermöglicht JavaScript, das in einer Seite läuft, eine HTTP-Anfrage an einen Server zu stellen, um bestimmte Ressourcen abzurufen. Wenn der Server sie bereitstellt, kann das JavaScript die Daten verwenden, um die Seite zu aktualisieren, üblicherweise durch Verwendung von DOM-Manipulations-APIs. Die angeforderten Daten sind oft JSON, das ein gutes Format für die Übertragung strukturierter Daten ist, können aber auch HTML oder nur Text sein.
Dies ist ein häufiges Muster für datengesteuerte Seiten wie Amazon, YouTube, eBay und so weiter. Mit diesem Modell:
- Seitenaktualisierungen erfolgen viel schneller und Sie müssen nicht auf das Neuladen der Seite warten, was bedeutet, dass die Website schneller und reaktionsschneller wirkt.
- Weniger Daten werden bei jeder Aktualisierung heruntergeladen, was weniger verschwendete Bandbreite bedeutet. Dies ist vielleicht kein großes Problem auf einem Desktop mit Breitbandverbindung, aber ein großes Problem auf mobilen Geräten und in Ländern ohne allgegenwärtigen schnellen Internetdienst.
Hinweis: In den frühen Tagen war diese allgemeine Technik als Asynchronously JavaScript and XML (AJAX) bekannt, da es dazu tendierte, XML-Daten anzufordern. Dies ist heutzutage normalerweise nicht der Fall (es wäre wahrscheinlicher, JSON anzufordern), aber das Ergebnis ist immer noch das gleiche, und der Begriff "AJAX" wird oft verwendet, um die Technik zu beschreiben.
Um die Geschwindigkeit weiter zu steigern, speichern einige Websites auch Ressourcen und Daten auf dem Computer des Nutzers, wenn sie das erste Mal angefordert werden, sodass bei nachfolgenden Besuchen die lokalen Versionen verwendet werden, statt jedes Mal neue Kopien herunterzuladen, wenn die Seite zuerst geladen wird. Der Inhalt wird nur vom Server neu geladen, wenn er aktualisiert wurde.
Die Fetch API
Lassen Sie uns einige Beispiele der Fetch API durchgehen.
Laden von Textinhalten
Für dieses Beispiel werden wir Daten aus einigen verschiedenen Textdateien anfordern und verwenden, um ein Inhaltsbereich zu füllen.
Diese Serie von Dateien wird als unsere Fake-Datenbank fungieren; in einer echten Anwendung würden wir eher eine serverseitige Sprache wie PHP, Python oder Node verwenden, um unsere Daten von einer Datenbank anzufordern. Hier möchten wir es jedoch einfach halten und uns auf den clientseitigen Teil konzentrieren.
Um dieses Beispiel zu beginnen, machen Sie eine lokale Kopie von fetch-start.html und den vier Textdateien — verse1.txt, verse2.txt, verse3.txt, und verse4.txt — in einem neuen Verzeichnis auf Ihrem Computer. In diesem Beispiel werden wir einen anderen Vers des Gedichts (den Sie wahrscheinlich erkennen werden) abrufen, wenn er im Dropdown-Menü ausgewählt wird.
Fügen Sie direkt im <script>
-Element den folgenden Code ein. Dieser speichert Verweise auf die <select>
- und <pre>
-Elemente und fügt einen Listener zum <select>
-Element hinzu, so dass, wenn der Nutzer einen neuen Wert auswählt, der neue Wert als Parameter an die Funktion updateDisplay()
übergeben wird.
const verseChoose = document.querySelector("select");
const poemDisplay = document.querySelector("pre");
verseChoose.addEventListener("change", () => {
const verse = verseChoose.value;
updateDisplay(verse);
});
Definieren wir unsere updateDisplay()
-Funktion. Zuerst setzen Sie den folgenden Code unter Ihren vorherigen Codeblock — dies ist die leere Hülle der Funktion.
function updateDisplay(verse) {
}
Wir starten unsere Funktion, indem wir eine relative URL konstruieren, die auf die Textdatei verweist, die wir laden möchten, da wir sie später benötigen. Der Wert des <select>
-Elements zu jeder Zeit ist derselbe wie der Text innerhalb der ausgewählten <option>
(es sei denn, Sie spezifizieren einen anderen Wert in einem value-Attribut) – zum Beispiel "Vers 1". Die entsprechende Vers-Textdatei ist "verse1.txt" und befindet sich im gleichen Verzeichnis wie die HTML-Datei, daher reicht der Dateiname allein aus.
Allerdings neigen Webserver dazu, case-sensitiv zu sein, und der Dateiname enthält kein Leerzeichen. Um "Vers 1" in "verse1.txt" zu konvertieren, müssen wir das 'V' in Kleinbuchstaben umwandeln, das Leerzeichen entfernen und ".txt" am Ende hinzufügen. Dies kann mit replace()
, toLowerCase()
, und Vorlagenliteral gemacht werden. Fügen Sie die folgenden Zeilen in Ihre updateDisplay()
-Funktion ein:
verse = verse.replace(" ", "").toLowerCase();
const url = `${verse}.txt`;
Schließlich sind wir bereit, die Fetch API zu verwenden:
// Call `fetch()`, passing in the URL.
fetch(url)
// fetch() returns a promise. When we have received a response from the server,
// the promise's `then()` handler is called with the response.
.then((response) => {
// Our handler throws an error if the request did not succeed.
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
// Otherwise (if the response succeeded), our handler fetches the response
// as text by calling response.text(), and immediately returns the promise
// returned by `response.text()`.
return response.text();
})
// When response.text() has succeeded, the `then()` handler is called with
// the text, and we copy it into the `poemDisplay` box.
.then((text) => {
poemDisplay.textContent = text;
})
// Catch any errors that might happen, and display a message
// in the `poemDisplay` box.
.catch((error) => {
poemDisplay.textContent = `Could not fetch verse: ${error}`;
});
Hier gibt es eine Menge zu entpacken.
Zunächst ist der Einstiegspunkt in die Fetch API eine globale Funktion namens fetch()
, die die URL als Parameter nimmt (sie nimmt einen weiteren optionalen Parameter für benutzerdefinierte Einstellungen, den wir hier jedoch nicht verwenden).
Außerdem ist fetch()
eine asynchrone API, die ein Promise
zurückgibt. Wenn Sie nicht wissen, was das ist, lesen Sie das Modul über asynchrones JavaScript, insbesondere die Lektion über Promises, und kehren Sie dann hierher zurück. In diesem Artikel wird auch die fetch()
-API behandelt!
Da fetch()
ein Promise zurückgibt, geben wir eine Funktion in die then()
-Methode des zurückgegebenen Promises ein. Diese Methode wird aufgerufen, wenn die HTTP-Anfrage eine Antwort vom Server erhalten hat. Im Handler überprüfen wir, ob die Anfrage erfolgreich war, und werfen einen Fehler, wenn nicht. Andernfalls rufen wir response.text()
auf, um den Antworttext zu erhalten.
Es stellt sich heraus, dass response.text()
auch asynchron ist, also geben wir das Promise zurück, das es zurückgibt, und übergeben eine Funktion in die then()
-Methode dieses neuen Promises. Diese Funktion wird aufgerufen, wenn der Antworttext bereit ist, und darin werden wir unseren <pre>
-Block mit dem Text aktualisieren.
Schließlich verketten wir einen catch()
-Handler am Ende, um Fehler abzufangen, die entweder in den aufgerufenen asynchronen Funktionen oder ihren Handlern geworfen werden.
Ein Problem mit dem Beispiel, wie es derzeit steht, ist, dass es keinen Teil des Gedichts zeigt, wenn es zuerst geladen wird. Um dies zu beheben, fügen Sie die folgenden zwei Zeilen am Ende Ihres Codes (direkt über dem schließenden </script>
-Tag) hinzu, um Vers 1 standardmäßig zu laden und sicherzustellen, dass das <select>
-Element immer den richtigen Wert zeigt:
updateDisplay("Verse 1");
verseChoose.value = "Verse 1";
Ihr Beispiel von einem Server ausführen
Moderne Browser werden keine HTTP-Anfragen ausführen, wenn Sie das Beispiel nur aus einer lokalen Datei ausführen. Dies liegt an Sicherheitsbeschränkungen (für mehr zum Thema Web-Sicherheit lesen Sie Website-Sicherheit).
Um dies zu umgehen, müssen wir das Beispiel testen, indem wir es über einen lokalen Webserver laufen lassen. Wie Sie das machen, erfahren Sie unter Wie richten Sie einen lokalen Testserver ein?.
Der Dosenladen
In diesem Beispiel haben wir eine Beispielseite namens Der Dosenladen erstellt – es ist ein fiktiver Supermarkt, der nur Dosengüter verkauft. Sie können dieses Beispiel live auf GitHub ansehen und den Quellcode einsehen.
Standardmäßig zeigt die Seite alle Produkte an, aber Sie können die Formularsteuerungen in der linken Spalte verwenden, um sie nach Kategorie, Suchbegriff oder beidem zu filtern.
Es gibt ziemlich viel komplexen Code, der sich mit dem Filtern der Produkte nach Kategorie und Suchbegriffen, dem Manipulieren von Zeichenfolgen, damit die Daten in der Benutzeroberfläche korrekt angezeigt werden, usw. befasst. Wir werden nicht alles im Artikel besprechen, aber Sie können umfangreiche Kommentare im Code finden (siehe can-script.js).
Wir werden jedoch den Fetch-Code erklären.
Der erste Block, der Fetch verwendet, befindet sich am Anfang des JavaScripts:
fetch("products.json")
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then((json) => initialize(json))
.catch((err) => console.error(`Fetch problem: ${err.message}`));
Die fetch()
-Funktion gibt ein Promise zurück. Wenn dieses erfolgreich abgeschlossen wird, enthält die Funktion innerhalb des ersten .then()
-Blocks die vom Netzwerk zurückgegebene response
.
Innerhalb dieser Funktion:
- überprüfen wir, dass der Server keinen Fehler (wie
404 Not Found
) zurückgegeben hat. Wenn doch, werfen wir den Fehler. - rufen wir
json()
auf demresponse
auf. Dies wird die Daten als JSON-Objekt abrufen. Wir geben das Promise zurück, das vonresponse.json()
zurückgegeben wird.
Als nächstes übergeben wir eine Funktion in die then()
-Methode des zurückgegebenen Promises. Diese Funktion erhält ein Objekt, das die Antwortdaten als JSON enthält, die wir in die initialize()
-Funktion übergeben. initialize()
beginnt mit dem Prozess der Anzeige aller Produkte in der Benutzeroberfläche.
Um Fehler zu behandeln, fügen wir einen .catch()
-Block an das Ende der Kette an. Dieser läuft, wenn das Promise aus irgendeinem Grund fehlschlägt. Innerhalb davon fügen wir eine Funktion hinzu, die ein Parameter, ein err
-Objekt, übergeben wird. Dieses err
-Objekt kann verwendet werden, um die Art des aufgetretenen Fehlers zu melden, in diesem Fall tun wir dies mit einem einfachen console.error()
.
Ein vollständiges Website würde diesen Fehler jedoch eleganter behandeln, indem eine Nachricht auf dem Bildschirm des Nutzers angezeigt wird und möglicherweise Optionen angeboten werden, um die Situation zu beheben, aber wir brauchen nicht mehr als ein einfaches console.error()
.
Sie können den Fehlerfall selbst testen:
- Machen Sie eine lokale Kopie der Beispiel Dateien.
- Führen Sie den Code durch einen Web Server aus (wie oben beschrieben, unter Ihr Beispiel von einem Server ausführen).
- Ändern Sie den Pfad zur Datei, die abgerufen werden soll, in etwas wie 'produc.json' (stellen Sie sicher, dass dies falsch geschrieben ist).
- Laden Sie nun die Indexdatei in Ihrem Browser (über
localhost:8000
) und schauen Sie in Ihre Entwicklerkonsole im Browser. Sie werden eine Nachricht ähnlich wie "Fetch problem: HTTP error: 404" sehen.
Der zweite Fetch-Block ist in der fetchBlob()
-Funktion zu finden:
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.blob();
})
.then((blob) => showProduct(blob, product))
.catch((err) => console.error(`Fetch problem: ${err.message}`));
Dies funktioniert ähnlich wie der vorherige, jedoch verwenden wir anstelle von json()
blob()
. In diesem Fall wollen wir unsere Antwort als Bilddatei zurückgeben, und das Datenformat, das wir dafür verwenden, ist Blob (der Begriff ist eine Abkürzung für "Binary Large Object" und kann im Wesentlichen verwendet werden, um große, wie Dateien geformte Objekte zu repräsentieren, wie Bilder oder Videodateien).
Sobald wir erfolgreich unseren Blob empfangen haben, übergeben wir ihn an unsere showProduct()
-Funktion, die ihn anzeigt.
Die XMLHttpRequest API
Manchmal, besonders in älterem Code, werden Sie eine andere API namens XMLHttpRequest
(oft als "XHR" abgekürzt) sehen, die verwendet wird, um HTTP-Anfragen zu stellen. Diese ging der Fetch voran und war wirklich die erste API, die weit verbreitet für die Implementierung von AJAX verwendet wurde. Wir empfehlen Ihnen, Fetch zu verwenden, wenn Sie können: Es ist eine einfachere API und hat mehr Funktionen als XMLHttpRequest
. Wir werden hier kein Beispiel verwenden, das XMLHttpRequest
nutzt, aber wir werden Ihnen zeigen, wie die XMLHttpRequest
-Version unserer ersten Dosenladen-Anfrage aussehen würde:
const request = new XMLHttpRequest();
try {
request.open("GET", "products.json");
request.responseType = "json";
request.addEventListener("load", () => initialize(request.response));
request.addEventListener("error", () => console.error("XHR error"));
request.send();
} catch (error) {
console.error(`XHR error ${request.status}`);
}
Es gibt fünf Stufen dazu:
- Erstellen Sie ein neues
XMLHttpRequest
-Objekt. - Rufen Sie seine
open()
-Methode auf, um es zu initialisieren. - Fügen Sie einen Ereignis-Listener zu seinem
load
-Ereignis hinzu, das ausgelöst wird, wenn die Antwort erfolgreich abgeschlossen wurde. Im Listener rufen wirinitialize()
mit den Daten auf. - Fügen Sie einen Ereignis-Listener zu seinem
error
-Ereignis hinzu, das ausgelöst wird, wenn die Anfrage auf einen Fehler stößt. - Senden Sie die Anfrage.
Wir müssen auch das Ganze in den try...catch-Block einwickeln, um alle von open()
oder send()
geworfenen Fehler zu behandeln.
Hoffentlich denken Sie, dass die Fetch API eine Verbesserung gegenüber diesem Code darstellt. Besonders berücksichtigen Sie, wie wir Fehler an zwei verschiedenen Stellen behandeln müssen.
Zusammenfassung
Dieser Artikel zeigt, wie man mit Fetch beginnt, um Daten vom Server abzurufen.
Siehe auch
Es gibt jedoch viele verschiedene Themen, die in diesem Artikel diskutiert werden, die wir nur an der Oberfläche gestreift haben. Für viel mehr Details zu diesen Themen versuchen Sie die folgenden Artikel: