Strömmar i Node.js kan vara komplicerade, men det är värt att ta dig tid att förstå dem.

Viktiga takeaways

  • Strömmar i Node.js är ett grundläggande verktyg för databearbetning och överföring, vilket gör dem idealiska för realtids- och händelsedrivna applikationer.
  • För att skapa en skrivbar ström i Node.js kan du använda fs-modulens createWriteStream()-funktion, som skriver data till en specifik plats.
  • Läsbar, skrivbar, duplex och transform är de fyra typerna av strömmar i Node.js, var och en med sitt eget användningsfall och funktionalitet.

En ström är ett grundläggande programmeringsverktyg som hanterar dataflödet. I sin kärna representerar en ström typiskt den sekventiella överföringen av bytes från en punkt till en annan. Node.js officiella dokumentation definierar en ström som ett abstrakt gränssnitt som du kan använda för att arbeta med data.

Att överföra data på en dator eller över ett nätverk är en idealisk användning av en ström.

Strömmar i Node.js

Strömmar har spelat en viktig roll i framgången för Node.js. De är idealiska för databehandling i realtid och händelsedrivna applikationer, två framträdande funktioner i Node.js runtime-miljö.

För att skapa en ny stream i Node.js måste du använda stream API, som enbart fungerar med Strings och Node.js buffertdata. Node.js har fyra typer av strömmar: skrivbar, läsbar, duplex och transform.

Hur man skapar och använder en skrivbar ström

En skrivbar ström låter dig skriva eller skicka data till en specifik plats. fs (filsystem)-modulen har en WriteStream-klass, som du kan använda för att skapa en ny ström med fs.createWriteStream() fungera. Den här funktionen accepterar sökvägen till filen du vill skriva data till, såväl som en valfri uppsättning alternativ.

const {createWriteStream} = require("fs");

(() => {
const file = "myFile.txt";
const myWriteStream = createWriteStream(file);
let x = 0;
const writeNumber = 10000;

const writeData = () => {
while (x < writeNumber) {
const chunk = Buffer.from(`${x}, `, "utf-8");
if (x writeNumber - 1) return myWriteStream.end(chunk);
if (!myWriteStream.write(chunk)) break;
x++
}
};

writeData();
})();

Denna kod importerar createWriteStream() funktion, vilken den anonyma pilfunktionen använder sedan för att skapa en ström som skriver data till myFile.txt. Den anonyma funktionen innehåller en inre funktion som kallas writeData() som skriver data.

De createWriteStream() Funktionen fungerar med en buffert för att skriva en samling nummer (0–9 999) till målfilen. Men när du kör skriptet ovan skapar det en fil i samma katalog som innehåller följande data:

Den nuvarande samlingen av nummer slutar på 2 915, men den borde ha inkluderat nummer upp till 9 999. Denna diskrepans uppstår eftersom varje WriteStream använder en buffert som lagrar en fast mängd data åt gången. För att lära dig vad detta standardvärde är, måste du konsultera högvattenmärke alternativ.

console.log("The highWaterMark value is: " +
myWriteStream.writableHighWaterMark + " bytes.");

Om du lägger till kodraden ovan till den anonyma funktionen kommer följande utdata i terminalen:

Terminalutgången visar att standard högvattenmärke värde (som är anpassningsbart) är 16 384 byte. Detta innebär att du bara kan lagra under 16 384 byte data i denna buffert åt gången. Så upp till nummer 2 915 (plus alla kommatecken och mellanslag) representerar den maximala mängden data som bufferten kan lagra på en gång.

Lösningen på buffertfelet är att använda en stream-händelse. En ström stöter på olika händelser i olika stadier av dataöverföringsprocessen. De dränera händelse är det lämpliga alternativet för denna situation.

I den writeData() funktionen ovan, anropet till WriteStreams write() funktion returnerar sant om databiten (eller intern buffert) är under högvattenmärke värde. Detta indikerar att applikationen kan skicka mer data till strömmen. Men så snart som skriva() funktionen returnerar false loop breaks eftersom du behöver tömma bufferten.

myWriteStream.on('drain', () => {
console.log("a drain has occurred...");
writeData();
});

Att sätta in dränera händelsekoden ovan i den anonyma funktionen kommer att tömma WriteStreams buffert när den är i full kapacitet. Sedan påminner den om writeData() metod, så att den kan fortsätta skriva data. Att köra det uppdaterade programmet kommer att producera följande utdata:

Du bör notera att ansökan var tvungen att tömma WriteStream-buffert tre gånger under dess genomförande. Textfilen upplevde också några förändringar:

Hur man skapar och använder en läsbar ström

För att läsa data, börja med att skapa en läsbar ström med hjälp av fs.createReadStream() fungera.

const {createReadStream} = require("fs");

(() => {
const file = "myFile.txt";
const myReadStream = createReadStream(file);

myReadStream.on("open", () => {
console.log(`The read stream has successfully opened ${file}.`);
});

myReadStream.on("data", chunk => {
console.log("The file contains the following data: " + chunk.toString());
});

myReadStream.on("close", () => {
console.log("The file has been successfully closed.");
});
})();

Skriptet ovan använder createReadStream() metod för att komma åt filen som den tidigare koden skapade: myFile.txt. De createReadStream() funktion accepterar en filsökväg (som kan vara i form av en sträng, buffert eller URL) och flera valfria alternativ som argument.

I den anonyma funktionen finns flera viktiga streamhändelser. Det finns dock inga tecken på dränera händelse. Detta beror på att en läsbar ström bara buffrar data när du anropar stream.push (bit) funktionen eller använd läsbar händelse.

De öppen händelsen utlöses när fs öppnar filen som du vill läsa från. När du fäster data händelse till en implicit kontinuerlig ström, får den strömmen att övergå till flytande läge. Detta gör att data kan passera så snart de blir tillgängliga. Att köra applikationen ovan ger följande utdata:

Hur man skapar och använder en Duplex Stream

En duplexström implementerar både skrivbara och läsbara strömgränssnitt, så att du kan läsa och skriva till en sådan ström. Ett exempel är en TCP-socket som förlitar sig på nätmodulen för att skapa den.

Ett enkelt sätt att demonstrera egenskaperna hos en duplexström är att skapa en TCP-server och klient som överför data.

Server.js-filen

const net = require('net');
const port = 5000;
const host = '127.0.0.1';

const server = net.createServer();

server.on('connection', (socket)=> {
console.log('Connection established from client.');

socket.on('data', (data) => {
console.log(data.toString());
});

socket.write("Hi client, I am server " + server.address().address);

socket.on('close', ()=> {
console.log('the socket is closed')
});
});

server.listen(port, host, () => {
console.log('TCP server is running on port: ' + port);
});

Filen client.js

const net = require('net');
const client = new net.Socket();
const port = 5000;
const host = '127.0.0.1';

client.connect(port, host, ()=> {
console.log("connected to server!");
client.write("Hi, I'm client " + client.address().address);
});

client.on('data', (data) => {
console.log(data.toString());
client.write("Goodbye");
client.end();
});

client.on('end', () => {
console.log('disconnected from server.');
});

Du kommer att märka att både server- och klientskripten använder en läsbar och skrivbar ström för att kommunicera (överföra och ta emot data). Självklart körs serverapplikationen först och börjar lyssna efter anslutningar. Så snart du startar klienten ansluter den till servern med hjälp av TCP-portnumret.

Efter att ha upprättat en anslutning initierar klienten dataöverföring genom att skriva till servern med sin WriteStream. Servern loggar data den tar emot till terminalen, sedan skriver den data med sin WriteStream. Slutligen loggar klienten data den tar emot, skriver ytterligare data och kopplar sedan från servern. Servern förblir öppen för andra klienter att ansluta.

Hur man skapar och använder en Transform Stream

Transformströmmar är duplexströmmar där utmatningen är relaterad till, men skiljer sig från, ingången. Node.js har två typer av Transform-strömmar: zlib- och kryptoströmmar. En zlib-ström kan komprimera en textfil och sedan dekomprimera den efter filöverföring.

Programmet compressFile.js

const zlib = require('zlib');
const { createReadStream, createWriteStream } = require('fs');

(() => {
const source = createReadStream('myFile.txt');
const destination = createWriteStream('myFile.txt.gz');

source.pipe(zlib.createGzip()).pipe(destination);
})();

Detta enkla skript tar den ursprungliga textfilen, komprimerar den och lagrar den i den aktuella katalogen. Detta är en enkel process tack vare den läsbara strömmen rör() metod. Stream pipelines tar bort användningen av buffertar och pipe data direkt från en stream till en annan.

Men innan data når den skrivbara strömmen i skriptet tar det en liten omväg via zlibs createGzip()-metod. Denna metod komprimerar filen och returnerar ett nytt Gzip-objekt som skrivströmmen sedan tar emot.

Applikationen decompressFile.js

const zlib = require('zlib'); 
const { createReadStream, createWriteStream } = require('fs');
 
(() => {
const source = createReadStream('myFile.txt.gz');
const destination = createWriteStream('myFile2.txt');

source.pipe(zlib.createUnzip()).pipe(destination);
})();

Det här skriptet ovan tar den komprimerade filen och dekomprimerar den. Om du öppnar den nya myFile2.txt fil, kommer du att se att den innehåller samma data som originalfilen:

Varför är strömmar viktiga?

Strömmar förbättrar effektiviteten i dataöverföringen. Läsbara och skrivbara strömmar fungerar som grunden som möjliggör kommunikation mellan klienter och servrar, samt komprimering och överföring av stora filer.

Strömmar förbättrar också prestandan för programmeringsspråk. Utan strömmar blir dataöverföringsprocessen mer komplex, vilket kräver större manuell input från utvecklare och resulterar i fler fel och prestandaproblem.