035.1 Lektion 1
Zertifikat: |
Web Development Essentials |
---|---|
Version: |
1.0 |
Thema: |
035 Node.js Server-Programmierung |
Lernziel: |
035.1 Node.js Grundlagen |
Lektion: |
1 von 1 |
Einführung
Node.js ist eine JavaScript-Laufzeitumgebung, die JavaScript-Code auf Webservern — dem so genannten Web Backend (Serverseite) — ausführt, anstatt eine zweite Sprache wie Python oder Ruby für serverseitige Programme zu verwenden. JavaScript wird bereits auf der modernen Frontend-Seite von Webanwendungen verwendet und interagiert mit dem HTML und CSS der Schnittstelle, die der Anwender mit einem Webbrowser nutzt. Node.js in Verbindung mit JavaScript im Browser bietet die Möglichkeit einer einzigen Programmiersprache für die gesamte Anwendung.
Der Hauptgrund für die Existenz von Node.js ist die Art und Weise, wie es mehrere gleichzeitige Verbindungen im Backend handhabt. Einer der häufigsten Wege, wie ein Webanwendungsserver Verbindungen verwaltet, ist die Ausführung mehrerer Prozesse. Wenn Sie eine Desktop-Anwendung auf Ihrem Computer öffnen, wird ein Prozess gestartet, der viele Ressourcen verbraucht. Stellen Sie sich nun vor, dass Tausende Benutzer auf diese Weise auf eine große Webanwendung zugreifen.
Node.js umgeht dieses Problem durch die sogenannte Event Loop, eine interne Schleife, die ständig überprüft, ob neue Aufgaben ankommen, die berechnet werden müssen. Dank der weiten Verbreitung von JavaScript und von Webtechnologien hat Node.js eine große Verbreitung sowohl in kleinen als auch in großen Anwendungen. Es gibt weitere Merkmale, die zu dieser weiten Verbreitung von Node.js beigetragen haben, z.B. die asynchrone und nicht blockierende Verarbeitung von Eingaben und Ausgaben (I/O), die später in dieser Lektion erläutert wird.
Die Node.js-Umgebung nutzt eine JavaScript-Engine zur Interpretation und Ausführung von JavaScript-Code auf der Serverseite oder auf dem Desktop. Unter diesen Bedingungen wird der vom Programmierer geschriebene JavaScript-Code geparst und just-in-time kompiliert, um die vom ursprünglichen JavaScript-Code erzeugten Maschinenbefehle auszuführen.
Note
|
Im diesen Lektionen über Node.js werden Sie vielleicht feststellen, dass das Node.js-JavaScript sich von dem unterscheidet, das im Browser läuft (das der ECMAScript-Spezifikation folgt), aber doch recht ähnlich ist. |
Erste Schritte
In diesem Abschnitt und den folgenden Beispielen gehen wir davon aus, dass Node.js bereits auf Ihrem Linux-System installiert ist und dass Sie über grundlegende Kenntnisse verfügen, z.B. die Ausführung von Befehlen im Terminal.
Um die folgenden Beispiele auszuführen, erstellen Sie ein Arbeitsverzeichnis namens node_examples
. Öffnen Sie ein Terminal und geben Sie node
ein. Wenn Node.js korrekt installiert ist, erscheint eine Eingabeaufforderung, in der Sie JavaScript-Befehle interaktiv testen können. Diese Art von Umgebung wird REPL genannt, was für “read, evaluate, print, and loop” steht. Geben Sie die folgenden Eingaben (oder einige andere JavaScript-Anweisungen) nach dem Prompt >
ein. Drücken Sie nach jeder Zeile die Eingabetaste, und die REPL-Umgebung wird die Ergebnisse ihrer Aktionen zurückgeben:
> let array = ['a', 'b', 'c', 'd']; undefined > array.map( (element, index) => (`Element: ${element} at index: ${index}`)); [ 'Element: a at index: 0', 'Element: b at index: 1', 'Element: c at index: 2', 'Element: d at index: 3' ] >
Das Snippet folgt der ES6-Syntax, die eine Map-Funktion bietet, um über das Array zu iterieren und die Ergebnisse mithilfe von String-Templates auszugeben. Sie können so ziemlich jeden gültigen Befehl schreiben. Um das Node.js-Terminal zu verlassen, geben Sie .exit
ein — vergessen Sie nicht den einleitenden Punkt!
Für längere Skripte und Module ist es bequemer, einen Texteditor wie VS Code, Emacs oder Vim zu verwenden. Speichern Sie die beiden eben gezeigten Codezeilen (mit einer kleinen Änderung) in einer Datei namens start.js
:
let array = ['a', 'b', 'c', 'd'];
array.map( (element, index) => ( console.log(`Element: ${element} at index: ${index}`)));
Führen Sie nun das Skript in der Shell aus, um dasselbe Ergebnis wie zuvor zu erhalten:
$ node ./start.js Element: a at index: 0 Element: b at index: 1 Element: c at index: 2 Element: d at index: 3
Bevor wir in den Code eintauchen, verschaffen wir uns einen Überblick über die Funktionsweise von Node.js, indem wir die Single-Thread-Ausführungsumgebung und die Event Loop nutzen.
Event Loop und Single Thread
Es ist schwer vorherzusagen, wie viel Zeit Ihr Node.js-Programm für die Bearbeitung einer Anfrage benötigen wird. Einige Anfragen können kurz sein – vielleicht nur eine Schleife durch Variablen im Speicher und deren Rückgabe –, während andere zeitaufwändige Aktivitäten erfordern, wie das Öffnen einer Datei im System oder das Auslösen einer Abfrage an eine Datenbank und das Warten auf die Ergebnisse. Wie geht Node.js mit dieser Unsicherheit um? Die Event Loop (Ereignisschleife) ist die Antwort.
Stellen Sie sich einen Koch vor: Das Backen eines Kuchens nimmt viel Zeit in Anspruch — der Koch wartet aber nicht die Backzeit im Ofen ab, um erst dann Kaffee zu kochen. Er kocht den Kaffee parallel und erledigt andere Aufgaben. Dabei prüft er ständig, wann der jeweils richtige Zeitpunkt ist, sich auf eine bestimmte Aufgabe zu konzentrieren.
Die Event Loop ist wie ein Koch, der ständig die Aktivitäten in seiner Umgebung im Blick hat. In Node.js prüft ein Event-Checker permanent, ob Operationen abgeschlossen sind oder darauf warten, von der JavaScript-Engine verarbeitet zu werden.
Bei diesem Ansatz blockiert eine asynchrone, lange Operation nicht andere, schnellere Operationen, die später folgen. Das liegt daran, dass der Mechanismus der Event Loop prüft, ob die lange Aufgabe, z.B. eine I/O-Operation, bereits abgeschlossen ist. Ist dies nicht der Fall, kann Node.js mit der Bearbeitung anderer Aufgaben fortfahren. Sobald die Hintergrundaufgabe abgeschlossen ist, werden die Ergebnisse zurückgegeben, und die auf Node.js aufsetzende Anwendung kann eine Triggerfunktion (Callback) verwenden, um die Ausgabe weiter zu verarbeiten.
Da Node.js mehrere Threads im Gegensatz zu anderen Umgebungen vermeidet, wird es als Single-Threaded Environment bezeichnet, und daher ist ein nicht-blockierender Ansatz von größter Bedeutung. Aus diesem Grund verwendet Node.js eine Event Loop. Für rechenintensive Aufgaben gehört Node.js jedoch nicht zu den besten Tools: Es gibt andere Programmiersprachen und Umgebungen, die diese Probleme effizienter lösen.
In den folgenden Abschnitten werden wir uns die Callback-Funktionen genauer ansehen. Machen Sie sich zunächst klar, dass Callback-Funktionen Auslöser sind, die nach Abschluss einer vordefinierten Operation ausgeführt werden.
Module
Es ist ein bewährtes Verfahren, komplexe Funktionen und umfangreiche Codeteile in kleinere Teile zu zerlegen. Diese Modularisierung hilft dabei, die Codebasis besser zu organisieren, die Implementierungen zu abstrahieren und komplizierte strukturelle Probleme zu vermeiden. Dafür verpacken Programmierer Codeblöcke, die von anderen internen oder externen Codeteilen verwendet werden können.
Nehmen wir das Beispiel eines Programms, das das Volumen einer Kugel berechnet. Öffnen Sie Ihren Texteditor und erstellen Sie eine Datei mit dem Namen volumeCalculator.js
, die das folgende JavaScript enthält:
const sphereVol = (radius) => {
return 4 / 3 * Math.PI * radius
}
console.log(`A sphere with radius 3 has a ${sphereVol(3)} volume.`);
console.log(`A sphere with radius 6 has a ${sphereVol(6)} volume.`);
Führen Sie nun die Datei mit Node aus:
$ node volumeCalculator.js A sphere with radius 3 has a 113.09733552923254 volume. A sphere with radius 6 has a 904.7786842338603 volume.
Wir nutzen eine einfache Funktion, um das Volumen einer Kugel auf der Grundlage ihres Radius zu berechnen. Nehmen wir nun an, wir müssen auch das Volumen eines Zylinders, Kegels usw. berechnen: Wir merken schnell, dass diese spezifischen Funktionen der Datei volumeCalculator.js
hinzugefügt werden müssen, die so zu einer umfangreichen Sammlung von Funktionen werden kann. Zur besseren Strukturierung nutzen Module als Pakete von separatem Code.
Dazu erstellen Sie eine separate Datei mit dem Namen polyhedrons.js
:
const coneVol = (radius, height) => {
return 1 / 3 * Math.PI * Math.pow(radius, 2) * height;
}
const cylinderVol = (radius, height) => {
return Math.PI * Math.pow(radius, 2) * height;
}
const sphereVol = (radius) => {
return 4 / 3 * Math.PI * Math.pow(radius, 3);
}
module.exports = {
coneVol,
cylinderVol,
sphereVol
}
Löschen Sie nun in der Datei volumeCalculator.js
den alten Code und ersetzen Sie ihn durch diesen Schnipsel:
const polyhedrons = require('./polyhedrons.js');
console.log(`A sphere with radius 3 has a ${polyhedrons.sphereVol(3)} volume.`);
console.log(`A cylinder with radius 3 and height 5 has a ${polyhedrons.cylinderVol(3, 5)} volume.`);
console.log(`A cone with radius 3 and height 5 has a ${polyhedrons.coneVol(3, 5)} volume.`);
Und dann führen Sie die Datei in der Node.js-Umgebung aus:
$ node volumeCalculator.js A sphere with radius 3 has a 113.09733552923254 volume. A cylinder with radius 3 and height 5 has a 141.3716694115407 volume. A cone with radius 3 and height 5 has a 47.12388980384689 volume.
In der Node.js-Umgebung wird jede Quellcodedatei als Modul betrachtet, aber das Wort “module” in Node.js steht für ein Code-Paket, das wie im vorigen Beispiel verpackt ist. Durch die Verwendung von Modulen haben wir die Volumenfunktionen aus der Hauptdatei volumeCalculator.js
herausgezogen, was deren Größe verringert und Unit-Tests erleichtert, was wiederum bei der Entwicklung von Anwendungen gute Praxis ist.
Nachdem wir nun wissen, wie Module in Node.js verwendet werden, können wir eines der wichtigsten Werkzeuge nutzen: den Node Package Manager (NPM).
Eine der Hauptaufgaben von NPM ist es, externe Module zu verwalten, herunterzuladen und im Projekt oder im Betriebssystem zu installieren. Sie initialisieren ein Node-Repository mit dem Befehl npm init
.
NPM stellt Standardfragen nach dem Namen Ihres Repositorys, der Version, der Beschreibung und so weiter. Sie können diese Schritte mit npm init --yes
überspringen. Der Befehl erzeugt dann automatisch eine Datei package.json
, die die Eigenschaften Ihres Projekts/Moduls beschreibt.
Öffnen Sie die Datei package.json
in Ihrem bevorzugten Texteditor und Sie sehen eine JSON-Datei, die Eigenschaften wie Schlüsselwörter, Skriptbefehle zur Verwendung mit NPM, einen Namen usw. enthält.
Eine dieser Eigenschaften sind die Abhängigkeiten, die in Ihrem lokalen Repository installiert sind. NPM fügt den Namen und die Version dieser Abhängigkeiten in die Datei package.json
ein, zusammen mit package-lock.json
, einer weiteren Datei, die von NPM als Fallback verwendet wird, falls package.json
fehlschlägt.
Geben Sie Folgendes in Ihr Terminal ein:
$ npm i dayjs
Das Flag i
ist eine Abkürzung für das Argument install
. Wenn Sie mit dem Internet verbunden sind, sucht NPM im Remote-Repository von Node.js nach einem Modul namens dayjs
, lädt es herunter und installiert es lokal. NPM fügt diese Abhängigkeit auch zu Ihren Dateien package.json
und package-lock.json
hinzu. Sie sehen nun ein Verzeichnis namens node_modules
, das das installierte sowie weitere Module enthält, falls diese benötigt werden. Das Verzeichnis node_modules
enthält den eigentlichen Code, der verwendet wird, wenn die Bibliothek importiert und aufgerufen wird. Allerdings wird dieser Ordner in Versionierungssystemen wie Git nicht gespeichert, da die Datei package.json
alle verwendeten Abhängigkeiten enthält. Ein anderer User nutzt die Datei package.json
und führt einfach npm install
auf seinem eigenen Rechner aus, wo NPM ein Verzeichnis node_modules
mit allen Abhängigkeiten in der package.json
erstellt und so die Versionskontrolle für Tausende von Dateien im NPM-Repository vermeidet.
Nachdem das Modul dayjs
im lokalen Verzeichnis installiert ist, öffnen Sie die Node.js-Konsole und geben Sie die folgenden Zeilen ein:
const dayjs = require('dayjs');
dayjs().format('YYYY MM-DDTHH:mm:ss')
Das Modul dayjs
wird mit dem Schlüsselwort require
geladen. Wenn eine Methode des Moduls aufgerufen wird, nimmt die Bibliothek die aktuelle Systemzeit und gibt sie im angegebenen Format aus:
2020 11-22T11:04:36
Dies ist der gleiche Mechanismus wie im vorherigen Beispiel, bei dem die Node.js-Laufzeitumgebung die Funktion eines Drittanbieters in den Code lädt.
Server-Funktionalität
Da Node.js das Backend von Webanwendungen steuert, besteht eine seiner Hauptaufgaben darin, HTTP-Anfragen zu verarbeiten.
Hier eine Zusammenfassung, wie Webserver eingehende HTTP-Anfragen behandeln. Die Funktion des Servers besteht darin, auf Anfragen zu warten, so schnell wie möglich die passende Antwort zu ermitteln und diese an den Absender der Anfrage zurückzusenden. Die Anwendung muss eine vom Benutzer ausgelöste eingehende HTTP-Anforderung empfangen, die Anforderung analysieren, die Berechnung durchführen, die Antwort generieren und sie zurücksenden. Ein HTTP-Modul wie Node.js vereinfacht diese Schritte und erlaubt es Ihnen, sich auf die Entwicklung der eigentlichen Anwendung zu konzentrieren.
Betrachten Sie das folgende Beispiel, das diese sehr grundlegende Funktionalität implementiert:
const http = require('http');
const url = require('url');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
const queryObject = url.parse(req.url,true).query;
let result = parseInt(queryObject.a) + parseInt(queryObject.b);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(`Result: ${result}\n`);
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
Speichern Sie diese Inhalte in einer Datei mit dem Namen basic_server.js
und führen Sie sie mit einem node
-Befehl aus. Das Terminal, auf dem Node.js läuft, wird die folgende Meldung anzeigen:
Server running at http://127.0.0.1:3000/
Rufen Sie dann die folgende URL in Ihrem Webbrowser auf: http://127.0.0.1:3000/numbers?a=2&b=17
Node.js ist ein Webserver, der auf Ihrem Computer läuft und zwei Module verwendet: http
und url
. Das http
-Modul richtet einen einfachen HTTP-Server ein, verarbeitet die eingehenden Webanfragen und übergibt sie an unseren einfachen Anwendungscode. Das URL-Modul analysiert die in der URL übergebenen Argumente, wandelt sie in ein Ganzzahlformat um und führt die Additionsoperation durch. Das http
-Modul sendet dann die Antwort als Text an den Webbrowser.
In einer echten Webanwendung wird Node.js üblicherweise verwendet, um Daten zu verarbeiten und abzurufen — in der Regel aus einer Datenbank — und die verarbeiteten Informationen an das Frontend zur Anzeige zurückzugeben. Die Basisanwendung in dieser Lektion zeigt jedoch kurz und bündig, wie Node.js Module verwendet, um Webanfragen als Webserver zu bearbeiten.
Geführte Übungen
-
Warum sollten Sie Module nutzen, anstatt einfache Funktionen zu schreiben?
-
Warum ist die Node.js-Umgebung so verbreitet? Nennen Sie ein Merkmal.
-
Wozu dient die Datei
package.json
? -
Warum ist es nicht empfehlenswert, das Verzeichnis
node_modules
zu speichern und weiterzugeben?
Offene Übungen
-
Wie führen Sie Node.js-Anwendungen auf Ihrem Computer aus?
-
Wie grenzen Sie Parameter in der URL ab, um sie innerhalb des Servers zu parsen?
-
Beschreiben Sie ein Szenario, in dem eine bestimmte Aufgabe einen Flaschenhals für eine Node.js-Anwendung darstellen könnte.
-
Wie implementieren Sie einen Parameter zum Multiplizieren oder Summieren der beiden Zahlen im Serverbeispiel?
Zusammenfassung
Diese Lektion gibt einen Überblick über die Node.js-Umgebung, ihre Eigenschaften und wie sie zur Implementierung einfacher Programme verwendet werden kann. Sie umfasst die folgenden Konzepte:
-
Was Node.js ist und warum es verwendet wird.
-
Wie man Node.js-Programme über die Kommandozeile ausführt.
-
Ereignisschleifen und der einzelne Thread.
-
Module.
-
Node Package Manager (NPM).
-
Server-Funktionalität.
Lösungen zu den geführten Übungen
-
Warum sollten Sie Module nutzen, anstatt einfache Funktionen zu schreiben?
Mit Modulen anstelle herkömmlicher Funktionen schaffen Sie eine einfachere Codebasis, die Sie lesen, pflegen und für die Sie automatisierte Tests schreiben können.
-
Warum ist die Node.js-Umgebung so verbreitet? Nennen Sie ein Merkmal.
Ein Grund ist die Flexibilität der Sprache JavaScript, die im Frontend von Webanwendungen bereits weit verbreitet ist. Node.js ermöglicht die Verwendung von nur einer Programmiersprache im gesamten System.
-
Wozu dient die Datei
package.json
?Diese Datei enthält Metadaten zum Projekt, z.B. den Namen, die Version, Abhängigkeiten (Bibliotheken) usw. Mit einer Datei
package.json
können andere dieselben Bibliotheken herunterladen und installieren und Tests auf dieselbe Weise durchführen wie der ursprüngliche Ersteller. -
Warum ist es nicht empfehlenswert, das Verzeichnis
node_modules
zu speichern und weiterzugeben?Das Verzeichnis
node_modules
enthält die Implementierungen von Bibliotheken in entfernten Repositories. Der bessere Weg, diese Bibliotheken gemeinsam zu nutzen, besteht darin, sie in der Dateipackage.json
anzugeben und dann NPM zu verwenden, um diese Bibliotheken herunterzuladen. Diese Methode ist einfacher und fehlerfreier, da Sie die Bibliotheken nicht lokal verfolgen und pflegen müssen.
Lösungen zu den offenen Übungen
-
Wie führen Sie Node.js-Anwendungen auf Ihrem Computer aus?
Sie führen sie aus, indem Sie
node PATH/FILE_NAME.js
in die Befehlszeile Ihres Terminals eingeben (PATH
in den Pfad Ihrer Node.js-Datei undFILE_NAME.js
in den von Ihnen gewählten Dateinamen ändern). -
Wie grenzen Sie Parameter in der URL ab, um sie innerhalb des Servers zu parsen?
Das kaufmännische Und-Zeichen
&
dient dazu, diese Parameter abzugrenzen, so dass sie im JavaScript-Code extrahiert und geparst werden können. -
Beschreiben Sie ein Szenario, in dem eine bestimmte Aufgabe einen Flaschenhals für eine Node.js-Anwendung darstellen könnte.
Node.js ist keine geeignete Umgebung für die Ausführung CPU-intensiver Prozesse, da es nur einen einzigen Thread verwendet. Ein Szenario mit numerischen Berechnungen könnte die gesamte Anwendung verlangsamen und blockieren. Wenn eine numerische Simulation erforderlich ist, sind andere Tools geeigneter.
-
Wie implementieren Sie einen Parameter zum Multiplizieren oder Summieren der beiden Zahlen im Serverbeispiel?
Verwenden Sie einen ternären Operator oder eine if-else-Bedingung, um nach einem zusätzlichen Parameter zu suchen. Wenn der Parameter die Zeichenkette
mult
ist, geben Sie das Produkt der Zahlen zurück, andernfalls die Summe. Ersetzen Sie den alten Code durch den unten stehenden Ausschnitt. Starten Sie den Server in der Befehlszeile über kbd:[Ctrl+C] neu und führen Sie den Befehl erneut aus, um den Server neu zu starten. Testen Sie nun die neue Anwendung, indem Sie die URLhttp://127.0.0.1:3000/numbers?a=2&b=17&operation=mult
in Ihrem Browser aufrufen. Wenn Sie den letzten Parameter weglassen oder ändern, sollte das Ergebnis jeweils die Summe der Zahlen sein.let result = queryObject.operation == 'mult' ? parseInt(queryObject.a) * parseInt(queryObject.b) : parseInt(queryObject.a) + parseInt(queryObject.b);