Computerhaus Quickborn

Tutorial: File Handling mit serverseitigem JavaScript​

Dieses File-Handling-Tutorial bringt JavaScript-Entwickler weiter.dee karen | shutterstock.com Mit Dateien auf einem Server zu arbeiten, ist für Softwareentwickler Alltag. Dafür bieten serverseitige JavaScript-Plattformen flexible und relativ simple Optionen. In diesem Tutorial erfahren Sie, wie Sie die fs-Bibliothek von JavaScript für die gängigsten File-Handling-Aufgaben einsetzen. Zum Beispiel: Dateien (im asynchronen oder synchronen Modus) lesen, schreiben, aktualisieren und verschieben, Verzeichnisse auflisten, oder Files in Blöcken streamen. (fs) – die Dateisystem-Bibliothek von JavaScript Die fs-Bibliothek ist ein Modul in JavaScript-Plattformen wie Node, Deno oder Bun. Sie müssen diese deshalb nicht über einen Package Manager wie NPM installieren, sondern können die Bibliothek einfach mit Hilfe von Skripten und ES6-Modulen importieren: import fs from 'fs'; Ein anderer Weg führt über Common.js-Syntax: const fs = require('fs'); Darüber hinaus erlauben neuere Node-Versionen auch, die Namespace-Version zu nutzen: const fs = require('node:fs'); Sobald fs installiert ist, stehen Ihnen zwei Möglichkeiten zur Verfügung, um mit dem Dateisystem zu interagieren: synchron oder asynchron: Während synchrones File Handling für simpleren Code sorgt, bietet die asynchrone Variante mehr Optimierungsmöglichkeiten auf Plattformebene (weil sie die Execution nicht blockiert). Letztgenannte Option ermöglicht zudem, Callbacks oder Promises einzusetzen. Dafür müssen allerdings auch die entsprechenden Packages installiert sein ('node:fs/promises'). Dateien erstellen Im ersten Schritt erstellen wir nun eine Datei (File). Unser write-Skript ist dabei write.mjs (das liegt daran, dass Node auf die Modul-Erweiterung .mjs besteht, wenn ES6-Module eingesetzt werden). Das resultierende File ist koan.txt – und enthält den Text einer Kurzgeschichte aus dem Zen-Buddhismus. import fs from 'node:fs'; const content = `A monk asked Chimon, “Before the lotus blossom has emerged from the water, what is it?” Chimon said, “A lotus blossom.” The monk pursued, “After it has come out of the water, what is it?" Chimon replied, “Lotus leaves."`; try { fs.writeFileSync('koan.txt', content); console.log('Koan file created!'); } catch (error) { console.error('Error creating file:', error); } Für den Fall, dass ein Fehler auftritt, nutzen wir einen try-Block als Wrapper, wenn wir eine Datei schreiben. Besonders einfach macht das die fs.writeFileSync()-Methode. Sie sehen auch die Teile von fs, die mit Destrukturierung importiert wurden. Zum Beispiel: import { writeFileSync } from ‘node:fs’; An dieser Stelle verzichten wir darauf, auf weitere fs-Funktionen einzugehen – etwa Dateieigenschaften und Kodierungen zu definieren. Die Standardeinstellungen sind für unsere Zwecke an dieser Stelle gut geeignet. Im nächsten Schritt werfen wir einen Blick darauf, wie das File asynchron geschrieben wird. Dabei nehmen wir an, dass es sich um dieselbe content-Variable handelt: // writeAsync.js const fs = require('fs'); const content = “...”; const filename = "asyncKoan.txt"; fs.writeFile(filename, content, (err) => { if (err) { console.error("Error writing file:", err); } else { console.log(`File '${filename}' written successfully!`); } }); In diesem Beispiel haben wir fs mit require von Common.js importiert – die Datei kann also eine simple .js-Extension sein. Dann haben wir einen asynchronen Ansatz mit Callbacks angewandt. Dieser akzeptiert das Argument err – das befüllt wird, wenn ein Fehler auftritt. Dasselbe lässt sich auch mit dem Promise-basierten Ansatz realisieren: // writePromise.js const fs = require('node:fs/promises'); const content = "..."; const filename = "promiseKoan.txt"; async function writeFileWithPromise() { try { await fs.writeFile(filename, content); console.log(`File '${filename}' written successfully!`); } catch (err) { console.error("Error writing file:", err); } } writeFileWithPromise(); In diesem Beispiel nutzen wir async/await, um das Promise synchron zu verarbeiten. Wir könnten dazu auch die Promise-then/catch-Handler direkt verwenden: // writePromiseDirect.js const fs = require('node:fs/promises'); const content = "..."; const filename = "promiseKoan.txt"; fs.writeFile(filename, content) .then(() => console.log(`File '${filename}' written successfully!`)) .catch((err) => console.error("Error writing file:", err)); Promises bieten maximale Flexibilität, allerdings auf Kosten einer etwas höheren Komplexität. Files lesen Wenn wir nun unser koan.txt-File mit dem synchronen Ansatz lesen möchten, gehen wir folgendermaßen vor: // readFile.mjs import { readFileSync } from 'node:fs'; let file = readFileSync('test.txt'); console.log(file.toString('utf8')); Wenn wir das File ausführen, erhalten wir: $ node readFile.mjs A monk asked Chimon… An dieser Stelle ist zu beachten, dass die Datei manuell per UTF8 in einen String dekodiert werden muss. Anderenfalls erhalten wir den Raw Buffer. Um dieselbe Aktion asynchron mit Callbacks durchzuführen, nutzen Sie folgenden Code: const fs = require('fs'); const filename = 'koan.txt'; fs.readFile(filename, (err, data) => { if (err) { console.error('Error reading file:', err); } else { console.log(data.toString('utf8')); } }); Dateien aktualisieren Ein File zu aktualisieren, funktioniert mit einer Kombination des bisher Behandelten. Die Datei wird gelesen, der Inhalt verändert und geschrieben. Ein Beispiel: const fs = require('fs'); const filename = 'koan.txt'; fs.readFile(filename, 'utf8', (err, data) => { if (err) { console.error('Error reading file:', err); return; } const updatedContent = data.replace(/lotus blossom/g, 'water lily'); fs.writeFile(filename, updatedContent, (err) => { if (err) { console.error('Error writing file:', err); } else { '${filename}' updated successfully!`); } }); }); Auch in diesem Beispiel verwenden wir den asynchronen Stil mit Callbacks. Dieser ist ganz allgemein vorzuziehen, wenn es darum geht, Dateien zu aktualisieren, weil das verhindert, dass der Event Loop während den Read- und Write-Schritten blockiert wird.   Textdaten-Formate Falls Sie eine Textdatei lesen und in strukturierter Form parsen müssen, können Sie dazu einfach den String benutzen, den Sie aus dem File extrahieren. Handelt es sich dabei beispielsweise um eine JSON-Datei, gehen Sie folgendermaßen vor: let myJson = JSON.parse(data); Dann modifizieren Sie: myJson.albumName = “Kind of Blue”; Und schreiben anschließend die neuen Informationen mit Hilfe von JSON.stringify(myJson). Ein ähnlicher Prozess könnte auch für andere Formate wie YAML, XML und CSV genutzt werden. Das würde allerdings auch eine Parsing-Bibliothek von einem Drittanbieter erfordern – insofern es effektiv ablaufen soll. Files löschen Eine Datei zu löschen, ist ein simpler Prozess. Mit dem synchronen Ansatz (der in der Regel ausreicht), geht das wie folgt: const fs = require('node:fs'); const filename = 'koan.txt'; try { fs.unlinkSync(filename); console.log(`File '${filename}' deleted successfully!`); } catch (err) { console.error('Error deleting file:', err); } Weil fs auf POSIX-ähnlichen Operationen basiert, wird der Löschvorgang der Datei mit „unlinkSync“ bezeichnet. Dabei wird die Verknüpfung der Datei im Dateisystem aufgehoben und so gelöscht. Nicht-Textdateien verarbeiten Dateien im Textformat sind die gängigste Form – allerdings ist JavaScript auch in der Lage, Binärdateien zu verarbeiten. Handelt es sich dabei um Bilder oder Audio-Dateien (oder etwas Exotischeres wie ein proprietäres Speicherformat in Games oder ein Firmware-Update), erfordert das, sich näher mit dem Pufferspeicher zu befassen. Das folgende simple Beispiel soll Ihnen einen Eindruck davon vermitteln, wie das funktioniert. Für unser Beispiel erstellen wir einfach ein Fake-Binary: const fs = require('fs'); const filename = 'binary.bin'; const buffer = Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]); fs.writeFile(filename, buffer, (err) => { if (err) { console.error('Error writing file:', err); return; } console.log(`Binary file '${filename}' written successfully!`); }); Der Output: const fs = require('fs'); const filename = 'binary.bin'; fs.readFile(filename, (err, data) => { if (err) { console.error('Error reading file:', err); return; } console.log(data); // process the Buffer data using Buffer methods (e.g., slice, copy) }); Files streamen Dateien in Blöcken (Chunks) zu streamen, ist eine weitere File-Handling-Facette, die insbesondere für den Umgang mit großen Dateien wichtig ist. Im Folgenden ein konstruiertes Beispiel, um in Streaming Chunks zu schreiben: const fs = require('fs'); const filename = 'large_file.txt'; const chunkSize = 1024 * 1024; // (1) const content = 'This is some content to be written in chunks.'; // (2) const fileSizeLimit = 5 * 1024 * 1024; // // (3) let writtenBytes = 0; // (4) const writeStream = fs.createWriteStream(filename, { highWaterMark: chunkSize }); // (5) function writeChunk() { // (6) const chunk = content.repeat(Math.ceil(chunkSize / content.length)); // (7) if (writtenBytes + chunk.length fileSizeLimit) { console.error('File size limit reached'); writeStream.end(); return; } console.log(`Wrote chunk of size: ${chunk.length}, Total written: ${writtenBytes}`); } } writeStream.on('error', (err) => { // (10) console.error('Error writing file:', err); }); writeStream.on('finish', () => { // (10) console.log('Finished writing file'); }); writeChunk(); Streaming erhöht die Performance, wirft aber auch Mehrarbeit auf. Dazu zählt, Blockgrößen zu definieren und auf Events zu reagieren, die darauf basieren. So lässt sich vermeiden, dass zu große Files auf einen Schlag in den Speicher geladen werden. Stattdessen werden die Dateien in kleine Teile zerlegt, von denen jedes einzelne bearbeitet wird. Im Folgenden einige Anmerkungen zu obenstehendem Code-Beispiel (die Zahlen korrespondieren mit den entsprechenden Vermerken am Ende der Code-Zeilen). Wir spezifizieren die Chunk-Größe in Kilobyte an. In diesem Fall handelt es sich um einen 1-MB-Block. Das heißt, es wird jeweils ein MB geschrieben. Fake-Content, um zu schreiben. Wir beschränken die Dateigröße – in diesem Fall auf 5 MB. Diese Variable trackt, wie viele Bytes wir geschrieben haben (damit die 5-MB-Grenze respektiert wird). Wir erstellen das eigentliche writeStream-Objekt. Das highWaterMark-Element gibt an, wie groß die Datenblöcke sind, die akzeptiert werden. Die writeChunk()-Funktion ist rekursiv: Immer, wenn ein Datenblock verarbeitet werden muss, ruft sie sich selbst auf – solange, bis das Limit erreicht ist. In diesem Fall wird die Funktion beendet. Hier wiederholen wir lediglich den Beispieltext, bis 1 MB erreicht ist. Kommen wir zum interessanten Teil. Wenn die Dateigröße nicht überschritten wird, rufen wir writeStream.write(chunk) auf: Wenn die Puffergröße überschritten wird, gibt writeStream.write(chunk) false zurück. Wird der Puffer überstrapaziert, tritt der drain-Event ein. Dieser wird vom ersten Handler verarbeitet, den wir hier mit writeStream.once('drain', writeChunk); definieren. Hierbei handelt es sich um einen rekursiven Callback von writeChunk. Das trackt, wie viel wir geschrieben haben. Wenn wir damit fertig sind zu schreiben, wird der Stream-Writer mit writeStream.end(); beendet. Veranschaulicht, wie Event Handler für error und finish hinzugefügt werden. Und von der Festplatte „zurückzulesen“, können wir eine ähnliche Methode einsetzen: const fs = require('fs'); const filename = 'large_file.txt'; const chunkSize = 1024 * 1024; // 1 MB chunk size again const readStream = fs.createReadStream(filename, { highWaterMark: chunkSize }); let totalBytesRead = 0; readStream.on('data', (chunk) => { totalBytesRead += chunk.length; console.log(`Received chunk of size: ${chunk.length}, Total read: ${totalBytesRead}`); // Other work on chunk }); readStream.on('error', (err) => { console.error('Error reading file:', err); }); readStream.on('end', () => { console.log('Finished reading file'); }); (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox! 

Tutorial: File Handling mit serverseitigem JavaScript​ Dieses File-Handling-Tutorial bringt JavaScript-Entwickler weiter.dee karen | shutterstock.com Mit Dateien auf einem Server zu arbeiten, ist für Softwareentwickler Alltag. Dafür bieten serverseitige JavaScript-Plattformen flexible und relativ simple Optionen. In diesem Tutorial erfahren Sie, wie Sie die fs-Bibliothek von JavaScript für die gängigsten File-Handling-Aufgaben einsetzen. Zum Beispiel: Dateien (im asynchronen oder synchronen Modus) lesen, schreiben, aktualisieren und verschieben, Verzeichnisse auflisten, oder Files in Blöcken streamen. (fs) – die Dateisystem-Bibliothek von JavaScript Die fs-Bibliothek ist ein Modul in JavaScript-Plattformen wie Node, Deno oder Bun. Sie müssen diese deshalb nicht über einen Package Manager wie NPM installieren, sondern können die Bibliothek einfach mit Hilfe von Skripten und ES6-Modulen importieren: import fs from 'fs'; Ein anderer Weg führt über Common.js-Syntax: const fs = require('fs'); Darüber hinaus erlauben neuere Node-Versionen auch, die Namespace-Version zu nutzen: const fs = require('node:fs'); Sobald fs installiert ist, stehen Ihnen zwei Möglichkeiten zur Verfügung, um mit dem Dateisystem zu interagieren: synchron oder asynchron: Während synchrones File Handling für simpleren Code sorgt, bietet die asynchrone Variante mehr Optimierungsmöglichkeiten auf Plattformebene (weil sie die Execution nicht blockiert). Letztgenannte Option ermöglicht zudem, Callbacks oder Promises einzusetzen. Dafür müssen allerdings auch die entsprechenden Packages installiert sein ('node:fs/promises'). Dateien erstellen Im ersten Schritt erstellen wir nun eine Datei (File). Unser write-Skript ist dabei write.mjs (das liegt daran, dass Node auf die Modul-Erweiterung .mjs besteht, wenn ES6-Module eingesetzt werden). Das resultierende File ist koan.txt – und enthält den Text einer Kurzgeschichte aus dem Zen-Buddhismus. import fs from 'node:fs'; const content = `A monk asked Chimon, “Before the lotus blossom has emerged from the water, what is it?” Chimon said, “A lotus blossom.” The monk pursued, “After it has come out of the water, what is it?" Chimon replied, “Lotus leaves."`; try { fs.writeFileSync('koan.txt', content); console.log('Koan file created!'); } catch (error) { console.error('Error creating file:', error); } Für den Fall, dass ein Fehler auftritt, nutzen wir einen try-Block als Wrapper, wenn wir eine Datei schreiben. Besonders einfach macht das die fs.writeFileSync()-Methode. Sie sehen auch die Teile von fs, die mit Destrukturierung importiert wurden. Zum Beispiel: import { writeFileSync } from ‘node:fs’; An dieser Stelle verzichten wir darauf, auf weitere fs-Funktionen einzugehen – etwa Dateieigenschaften und Kodierungen zu definieren. Die Standardeinstellungen sind für unsere Zwecke an dieser Stelle gut geeignet. Im nächsten Schritt werfen wir einen Blick darauf, wie das File asynchron geschrieben wird. Dabei nehmen wir an, dass es sich um dieselbe content-Variable handelt: // writeAsync.js const fs = require('fs'); const content = “...”; const filename = "asyncKoan.txt"; fs.writeFile(filename, content, (err) => { if (err) { console.error("Error writing file:", err); } else { console.log(`File '${filename}' written successfully!`); } }); In diesem Beispiel haben wir fs mit require von Common.js importiert – die Datei kann also eine simple .js-Extension sein. Dann haben wir einen asynchronen Ansatz mit Callbacks angewandt. Dieser akzeptiert das Argument err – das befüllt wird, wenn ein Fehler auftritt. Dasselbe lässt sich auch mit dem Promise-basierten Ansatz realisieren: // writePromise.js const fs = require('node:fs/promises'); const content = "..."; const filename = "promiseKoan.txt"; async function writeFileWithPromise() { try { await fs.writeFile(filename, content); console.log(`File '${filename}' written successfully!`); } catch (err) { console.error("Error writing file:", err); } } writeFileWithPromise(); In diesem Beispiel nutzen wir async/await, um das Promise synchron zu verarbeiten. Wir könnten dazu auch die Promise-then/catch-Handler direkt verwenden: // writePromiseDirect.js const fs = require('node:fs/promises'); const content = "..."; const filename = "promiseKoan.txt"; fs.writeFile(filename, content) .then(() => console.log(`File '${filename}' written successfully!`)) .catch((err) => console.error("Error writing file:", err)); Promises bieten maximale Flexibilität, allerdings auf Kosten einer etwas höheren Komplexität. Files lesen Wenn wir nun unser koan.txt-File mit dem synchronen Ansatz lesen möchten, gehen wir folgendermaßen vor: // readFile.mjs import { readFileSync } from 'node:fs'; let file = readFileSync('test.txt'); console.log(file.toString('utf8')); Wenn wir das File ausführen, erhalten wir: $ node readFile.mjs A monk asked Chimon… An dieser Stelle ist zu beachten, dass die Datei manuell per UTF8 in einen String dekodiert werden muss. Anderenfalls erhalten wir den Raw Buffer. Um dieselbe Aktion asynchron mit Callbacks durchzuführen, nutzen Sie folgenden Code: const fs = require('fs'); const filename = 'koan.txt'; fs.readFile(filename, (err, data) => { if (err) { console.error('Error reading file:', err); } else { console.log(data.toString('utf8')); } }); Dateien aktualisieren Ein File zu aktualisieren, funktioniert mit einer Kombination des bisher Behandelten. Die Datei wird gelesen, der Inhalt verändert und geschrieben. Ein Beispiel: const fs = require('fs'); const filename = 'koan.txt'; fs.readFile(filename, 'utf8', (err, data) => { if (err) { console.error('Error reading file:', err); return; } const updatedContent = data.replace(/lotus blossom/g, 'water lily'); fs.writeFile(filename, updatedContent, (err) => { if (err) { console.error('Error writing file:', err); } else { '${filename}' updated successfully!`); } }); }); Auch in diesem Beispiel verwenden wir den asynchronen Stil mit Callbacks. Dieser ist ganz allgemein vorzuziehen, wenn es darum geht, Dateien zu aktualisieren, weil das verhindert, dass der Event Loop während den Read- und Write-Schritten blockiert wird.   Textdaten-Formate Falls Sie eine Textdatei lesen und in strukturierter Form parsen müssen, können Sie dazu einfach den String benutzen, den Sie aus dem File extrahieren. Handelt es sich dabei beispielsweise um eine JSON-Datei, gehen Sie folgendermaßen vor: let myJson = JSON.parse(data); Dann modifizieren Sie: myJson.albumName = “Kind of Blue”; Und schreiben anschließend die neuen Informationen mit Hilfe von JSON.stringify(myJson). Ein ähnlicher Prozess könnte auch für andere Formate wie YAML, XML und CSV genutzt werden. Das würde allerdings auch eine Parsing-Bibliothek von einem Drittanbieter erfordern – insofern es effektiv ablaufen soll. Files löschen Eine Datei zu löschen, ist ein simpler Prozess. Mit dem synchronen Ansatz (der in der Regel ausreicht), geht das wie folgt: const fs = require('node:fs'); const filename = 'koan.txt'; try { fs.unlinkSync(filename); console.log(`File '${filename}' deleted successfully!`); } catch (err) { console.error('Error deleting file:', err); } Weil fs auf POSIX-ähnlichen Operationen basiert, wird der Löschvorgang der Datei mit „unlinkSync“ bezeichnet. Dabei wird die Verknüpfung der Datei im Dateisystem aufgehoben und so gelöscht. Nicht-Textdateien verarbeiten Dateien im Textformat sind die gängigste Form – allerdings ist JavaScript auch in der Lage, Binärdateien zu verarbeiten. Handelt es sich dabei um Bilder oder Audio-Dateien (oder etwas Exotischeres wie ein proprietäres Speicherformat in Games oder ein Firmware-Update), erfordert das, sich näher mit dem Pufferspeicher zu befassen. Das folgende simple Beispiel soll Ihnen einen Eindruck davon vermitteln, wie das funktioniert. Für unser Beispiel erstellen wir einfach ein Fake-Binary: const fs = require('fs'); const filename = 'binary.bin'; const buffer = Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]); fs.writeFile(filename, buffer, (err) => { if (err) { console.error('Error writing file:', err); return; } console.log(`Binary file '${filename}' written successfully!`); }); Der Output: const fs = require('fs'); const filename = 'binary.bin'; fs.readFile(filename, (err, data) => { if (err) { console.error('Error reading file:', err); return; } console.log(data); // process the Buffer data using Buffer methods (e.g., slice, copy) }); Files streamen Dateien in Blöcken (Chunks) zu streamen, ist eine weitere File-Handling-Facette, die insbesondere für den Umgang mit großen Dateien wichtig ist. Im Folgenden ein konstruiertes Beispiel, um in Streaming Chunks zu schreiben: const fs = require('fs'); const filename = 'large_file.txt'; const chunkSize = 1024 * 1024; // (1) const content = 'This is some content to be written in chunks.'; // (2) const fileSizeLimit = 5 * 1024 * 1024; // // (3) let writtenBytes = 0; // (4) const writeStream = fs.createWriteStream(filename, { highWaterMark: chunkSize }); // (5) function writeChunk() { // (6) const chunk = content.repeat(Math.ceil(chunkSize / content.length)); // (7) if (writtenBytes + chunk.length fileSizeLimit) { console.error('File size limit reached'); writeStream.end(); return; } console.log(`Wrote chunk of size: ${chunk.length}, Total written: ${writtenBytes}`); } } writeStream.on('error', (err) => { // (10) console.error('Error writing file:', err); }); writeStream.on('finish', () => { // (10) console.log('Finished writing file'); }); writeChunk(); Streaming erhöht die Performance, wirft aber auch Mehrarbeit auf. Dazu zählt, Blockgrößen zu definieren und auf Events zu reagieren, die darauf basieren. So lässt sich vermeiden, dass zu große Files auf einen Schlag in den Speicher geladen werden. Stattdessen werden die Dateien in kleine Teile zerlegt, von denen jedes einzelne bearbeitet wird. Im Folgenden einige Anmerkungen zu obenstehendem Code-Beispiel (die Zahlen korrespondieren mit den entsprechenden Vermerken am Ende der Code-Zeilen). Wir spezifizieren die Chunk-Größe in Kilobyte an. In diesem Fall handelt es sich um einen 1-MB-Block. Das heißt, es wird jeweils ein MB geschrieben. Fake-Content, um zu schreiben. Wir beschränken die Dateigröße – in diesem Fall auf 5 MB. Diese Variable trackt, wie viele Bytes wir geschrieben haben (damit die 5-MB-Grenze respektiert wird). Wir erstellen das eigentliche writeStream-Objekt. Das highWaterMark-Element gibt an, wie groß die Datenblöcke sind, die akzeptiert werden. Die writeChunk()-Funktion ist rekursiv: Immer, wenn ein Datenblock verarbeitet werden muss, ruft sie sich selbst auf – solange, bis das Limit erreicht ist. In diesem Fall wird die Funktion beendet. Hier wiederholen wir lediglich den Beispieltext, bis 1 MB erreicht ist. Kommen wir zum interessanten Teil. Wenn die Dateigröße nicht überschritten wird, rufen wir writeStream.write(chunk) auf: Wenn die Puffergröße überschritten wird, gibt writeStream.write(chunk) false zurück. Wird der Puffer überstrapaziert, tritt der drain-Event ein. Dieser wird vom ersten Handler verarbeitet, den wir hier mit writeStream.once('drain', writeChunk); definieren. Hierbei handelt es sich um einen rekursiven Callback von writeChunk. Das trackt, wie viel wir geschrieben haben. Wenn wir damit fertig sind zu schreiben, wird der Stream-Writer mit writeStream.end(); beendet. Veranschaulicht, wie Event Handler für error und finish hinzugefügt werden. Und von der Festplatte „zurückzulesen“, können wir eine ähnliche Methode einsetzen: const fs = require('fs'); const filename = 'large_file.txt'; const chunkSize = 1024 * 1024; // 1 MB chunk size again const readStream = fs.createReadStream(filename, { highWaterMark: chunkSize }); let totalBytesRead = 0; readStream.on('data', (chunk) => { totalBytesRead += chunk.length; console.log(`Received chunk of size: ${chunk.length}, Total read: ${totalBytesRead}`); // Other work on chunk }); readStream.on('error', (err) => { console.error('Error reading file:', err); }); readStream.on('end', () => { console.log('Finished reading file'); }); (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!

Tutorial: File Handling mit serverseitigem JavaScript​

Dieses File-Handling-Tutorial bringt JavaScript-Entwickler weiter.dee karen | shutterstock.com Mit Dateien auf einem Server zu arbeiten, ist für Softwareentwickler Alltag. Dafür bieten serverseitige JavaScript-Plattformen flexible und relativ simple Optionen. In diesem Tutorial erfahren Sie, wie Sie die fs-Bibliothek von JavaScript für die gängigsten File-Handling-Aufgaben einsetzen. Zum Beispiel: Dateien (im asynchronen oder synchronen Modus) lesen, schreiben, aktualisieren und verschieben, Verzeichnisse auflisten, oder Files in Blöcken streamen. (fs) – die Dateisystem-Bibliothek von JavaScript Die fs-Bibliothek ist ein Modul in JavaScript-Plattformen wie Node, Deno oder Bun. Sie müssen diese deshalb nicht über einen Package Manager wie NPM installieren, sondern können die Bibliothek einfach mit Hilfe von Skripten und ES6-Modulen importieren: import fs from ‘fs’; Ein anderer Weg führt über Common.js-Syntax: const fs = require(‘fs’); Darüber hinaus erlauben neuere Node-Versionen auch, die Namespace-Version zu nutzen: const fs = require(‘node:fs’); Sobald fs installiert ist, stehen Ihnen zwei Möglichkeiten zur Verfügung, um mit dem Dateisystem zu interagieren: synchron oder asynchron: Während synchrones File Handling für simpleren Code sorgt, bietet die asynchrone Variante mehr Optimierungsmöglichkeiten auf Plattformebene (weil sie die Execution nicht blockiert). Letztgenannte Option ermöglicht zudem, Callbacks oder Promises einzusetzen. Dafür müssen allerdings auch die entsprechenden Packages installiert sein (‘node:fs/promises’). Dateien erstellen Im ersten Schritt erstellen wir nun eine Datei (File). Unser write-Skript ist dabei write.mjs (das liegt daran, dass Node auf die Modul-Erweiterung .mjs besteht, wenn ES6-Module eingesetzt werden). Das resultierende File ist koan.txt – und enthält den Text einer Kurzgeschichte aus dem Zen-Buddhismus. import fs from ‘node:fs’; const content = `A monk asked Chimon, “Before the lotus blossom has emerged from the water, what is it?” Chimon said, “A lotus blossom.” The monk pursued, “After it has come out of the water, what is it?” Chimon replied, “Lotus leaves.”`; try { fs.writeFileSync(‘koan.txt’, content); console.log(‘Koan file created!’); } catch (error) { console.error(‘Error creating file:’, error); } Für den Fall, dass ein Fehler auftritt, nutzen wir einen try-Block als Wrapper, wenn wir eine Datei schreiben. Besonders einfach macht das die fs.writeFileSync()-Methode. Sie sehen auch die Teile von fs, die mit Destrukturierung importiert wurden. Zum Beispiel: import { writeFileSync } from ‘node:fs’; An dieser Stelle verzichten wir darauf, auf weitere fs-Funktionen einzugehen – etwa Dateieigenschaften und Kodierungen zu definieren. Die Standardeinstellungen sind für unsere Zwecke an dieser Stelle gut geeignet. Im nächsten Schritt werfen wir einen Blick darauf, wie das File asynchron geschrieben wird. Dabei nehmen wir an, dass es sich um dieselbe content-Variable handelt: // writeAsync.js const fs = require(‘fs’); const content = “…”; const filename = “asyncKoan.txt”; fs.writeFile(filename, content, (err) => { if (err) { console.error(“Error writing file:”, err); } else { console.log(`File ‘${filename}’ written successfully!`); } }); In diesem Beispiel haben wir fs mit require von Common.js importiert – die Datei kann also eine simple .js-Extension sein. Dann haben wir einen asynchronen Ansatz mit Callbacks angewandt. Dieser akzeptiert das Argument err – das befüllt wird, wenn ein Fehler auftritt. Dasselbe lässt sich auch mit dem Promise-basierten Ansatz realisieren: // writePromise.js const fs = require(‘node:fs/promises’); const content = “…”; const filename = “promiseKoan.txt”; async function writeFileWithPromise() { try { await fs.writeFile(filename, content); console.log(`File ‘${filename}’ written successfully!`); } catch (err) { console.error(“Error writing file:”, err); } } writeFileWithPromise(); In diesem Beispiel nutzen wir async/await, um das Promise synchron zu verarbeiten. Wir könnten dazu auch die Promise-then/catch-Handler direkt verwenden: // writePromiseDirect.js const fs = require(‘node:fs/promises’); const content = “…”; const filename = “promiseKoan.txt”; fs.writeFile(filename, content) .then(() => console.log(`File ‘${filename}’ written successfully!`)) .catch((err) => console.error(“Error writing file:”, err)); Promises bieten maximale Flexibilität, allerdings auf Kosten einer etwas höheren Komplexität. Files lesen Wenn wir nun unser koan.txt-File mit dem synchronen Ansatz lesen möchten, gehen wir folgendermaßen vor: // readFile.mjs import { readFileSync } from ‘node:fs’; let file = readFileSync(‘test.txt’); console.log(file.toString(‘utf8’)); Wenn wir das File ausführen, erhalten wir: $ node readFile.mjs A monk asked Chimon… An dieser Stelle ist zu beachten, dass die Datei manuell per UTF8 in einen String dekodiert werden muss. Anderenfalls erhalten wir den Raw Buffer. Um dieselbe Aktion asynchron mit Callbacks durchzuführen, nutzen Sie folgenden Code: const fs = require(‘fs’); const filename = ‘koan.txt’; fs.readFile(filename, (err, data) => { if (err) { console.error(‘Error reading file:’, err); } else { console.log(data.toString(‘utf8’)); } }); Dateien aktualisieren Ein File zu aktualisieren, funktioniert mit einer Kombination des bisher Behandelten. Die Datei wird gelesen, der Inhalt verändert und geschrieben. Ein Beispiel: const fs = require(‘fs’); const filename = ‘koan.txt’; fs.readFile(filename, ‘utf8’, (err, data) => { if (err) { console.error(‘Error reading file:’, err); return; } const updatedContent = data.replace(/lotus blossom/g, ‘water lily’); fs.writeFile(filename, updatedContent, (err) => { if (err) { console.error(‘Error writing file:’, err); } else { ‘${filename}’ updated successfully!`); } }); }); Auch in diesem Beispiel verwenden wir den asynchronen Stil mit Callbacks. Dieser ist ganz allgemein vorzuziehen, wenn es darum geht, Dateien zu aktualisieren, weil das verhindert, dass der Event Loop während den Read- und Write-Schritten blockiert wird.   Textdaten-Formate Falls Sie eine Textdatei lesen und in strukturierter Form parsen müssen, können Sie dazu einfach den String benutzen, den Sie aus dem File extrahieren. Handelt es sich dabei beispielsweise um eine JSON-Datei, gehen Sie folgendermaßen vor: let myJson = JSON.parse(data); Dann modifizieren Sie: myJson.albumName = “Kind of Blue”; Und schreiben anschließend die neuen Informationen mit Hilfe von JSON.stringify(myJson). Ein ähnlicher Prozess könnte auch für andere Formate wie YAML, XML und CSV genutzt werden. Das würde allerdings auch eine Parsing-Bibliothek von einem Drittanbieter erfordern – insofern es effektiv ablaufen soll. Files löschen Eine Datei zu löschen, ist ein simpler Prozess. Mit dem synchronen Ansatz (der in der Regel ausreicht), geht das wie folgt: const fs = require(‘node:fs’); const filename = ‘koan.txt’; try { fs.unlinkSync(filename); console.log(`File ‘${filename}’ deleted successfully!`); } catch (err) { console.error(‘Error deleting file:’, err); } Weil fs auf POSIX-ähnlichen Operationen basiert, wird der Löschvorgang der Datei mit „unlinkSync“ bezeichnet. Dabei wird die Verknüpfung der Datei im Dateisystem aufgehoben und so gelöscht. Nicht-Textdateien verarbeiten Dateien im Textformat sind die gängigste Form – allerdings ist JavaScript auch in der Lage, Binärdateien zu verarbeiten. Handelt es sich dabei um Bilder oder Audio-Dateien (oder etwas Exotischeres wie ein proprietäres Speicherformat in Games oder ein Firmware-Update), erfordert das, sich näher mit dem Pufferspeicher zu befassen. Das folgende simple Beispiel soll Ihnen einen Eindruck davon vermitteln, wie das funktioniert. Für unser Beispiel erstellen wir einfach ein Fake-Binary: const fs = require(‘fs’); const filename = ‘binary.bin’; const buffer = Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]); fs.writeFile(filename, buffer, (err) => { if (err) { console.error(‘Error writing file:’, err); return; } console.log(`Binary file ‘${filename}’ written successfully!`); }); Der Output: const fs = require(‘fs’); const filename = ‘binary.bin’; fs.readFile(filename, (err, data) => { if (err) { console.error(‘Error reading file:’, err); return; } console.log(data); // process the Buffer data using Buffer methods (e.g., slice, copy) }); Files streamen Dateien in Blöcken (Chunks) zu streamen, ist eine weitere File-Handling-Facette, die insbesondere für den Umgang mit großen Dateien wichtig ist. Im Folgenden ein konstruiertes Beispiel, um in Streaming Chunks zu schreiben: const fs = require(‘fs’); const filename = ‘large_file.txt’; const chunkSize = 1024 * 1024; // (1) const content = ‘This is some content to be written in chunks.’; // (2) const fileSizeLimit = 5 * 1024 * 1024; // // (3) let writtenBytes = 0; // (4) const writeStream = fs.createWriteStream(filename, { highWaterMark: chunkSize }); // (5) function writeChunk() { // (6) const chunk = content.repeat(Math.ceil(chunkSize / content.length)); // (7) if (writtenBytes + chunk.length fileSizeLimit) { console.error(‘File size limit reached’); writeStream.end(); return; } console.log(`Wrote chunk of size: ${chunk.length}, Total written: ${writtenBytes}`); } } writeStream.on(‘error’, (err) => { // (10) console.error(‘Error writing file:’, err); }); writeStream.on(‘finish’, () => { // (10) console.log(‘Finished writing file’); }); writeChunk(); Streaming erhöht die Performance, wirft aber auch Mehrarbeit auf. Dazu zählt, Blockgrößen zu definieren und auf Events zu reagieren, die darauf basieren. So lässt sich vermeiden, dass zu große Files auf einen Schlag in den Speicher geladen werden. Stattdessen werden die Dateien in kleine Teile zerlegt, von denen jedes einzelne bearbeitet wird. Im Folgenden einige Anmerkungen zu obenstehendem Code-Beispiel (die Zahlen korrespondieren mit den entsprechenden Vermerken am Ende der Code-Zeilen). Wir spezifizieren die Chunk-Größe in Kilobyte an. In diesem Fall handelt es sich um einen 1-MB-Block. Das heißt, es wird jeweils ein MB geschrieben. Fake-Content, um zu schreiben. Wir beschränken die Dateigröße – in diesem Fall auf 5 MB. Diese Variable trackt, wie viele Bytes wir geschrieben haben (damit die 5-MB-Grenze respektiert wird). Wir erstellen das eigentliche writeStream-Objekt. Das highWaterMark-Element gibt an, wie groß die Datenblöcke sind, die akzeptiert werden. Die writeChunk()-Funktion ist rekursiv: Immer, wenn ein Datenblock verarbeitet werden muss, ruft sie sich selbst auf – solange, bis das Limit erreicht ist. In diesem Fall wird die Funktion beendet. Hier wiederholen wir lediglich den Beispieltext, bis 1 MB erreicht ist. Kommen wir zum interessanten Teil. Wenn die Dateigröße nicht überschritten wird, rufen wir writeStream.write(chunk) auf: Wenn die Puffergröße überschritten wird, gibt writeStream.write(chunk) false zurück. Wird der Puffer überstrapaziert, tritt der drain-Event ein. Dieser wird vom ersten Handler verarbeitet, den wir hier mit writeStream.once(‘drain’, writeChunk); definieren. Hierbei handelt es sich um einen rekursiven Callback von writeChunk. Das trackt, wie viel wir geschrieben haben. Wenn wir damit fertig sind zu schreiben, wird der Stream-Writer mit writeStream.end(); beendet. Veranschaulicht, wie Event Handler für error und finish hinzugefügt werden. Und von der Festplatte „zurückzulesen“, können wir eine ähnliche Methode einsetzen: const fs = require(‘fs’); const filename = ‘large_file.txt’; const chunkSize = 1024 * 1024; // 1 MB chunk size again const readStream = fs.createReadStream(filename, { highWaterMark: chunkSize }); let totalBytesRead = 0; readStream.on(‘data’, (chunk) => { totalBytesRead += chunk.length; console.log(`Received chunk of size: ${chunk.length}, Total read: ${totalBytesRead}`); // Other work on chunk }); readStream.on(‘error’, (err) => { console.error(‘Error reading file:’, err); }); readStream.on(‘end’, () => { console.log(‘Finished reading file’); }); (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox! 

Nach oben scrollen