Contents

En introduktion till användning av strömmar i Node.js

Viktiga lärdomar

Strömmar spelar en viktig roll i Node.js eftersom de underlättar effektiv databehandling och överföring och därmed ger optimalt stöd för realtids- och händelsestyrda applikationer.

Med hjälp av Node.js filssystemmodul kan man använda funktionen createWriteStream() för att skapa en skrivbar ström som riktar data mot ett visst område.

Node.js-miljön omfattar en mängd olika strömtyper som tillgodoser olika syften och krav. Dessa fyra primära kategorier inkluderar läsbara, skrivbara, duplexa och transformerade strömmar. Varje typ har en tydlig funktion och specifika egenskaper, vilket gör att utvecklare kan välja det lämpligaste alternativet för sina specifika applikationsbehov.

En stream är en viktig programmeringskonstruktion för att underlätta kontinuerlig överföring av information från en plats till en annan. Konceptet med en stream kretsar i huvudsak kring systematisk överföring av bytes längs en förutbestämd väg. Enligt Node.js auktoritativa resurser utgör en stream ett hypotetiskt ramverk som gör det möjligt för användare att interagera med och manipulera data.

Den effektiva överföringen av information via en stream är ett utmärkt exempel på dess tillämpning i datorsystem och nätverkskommunikation.

Strömmar i Node.js

Strömmar har varit en avgörande faktor för Node.js framgångar på grund av deras lämplighet för databehandling i realtid och händelsestyrda applikationer, som båda är grundläggande aspekter av Node.js runtime-miljö.

För att skapa ett nytt flöde i Node.js måste man använda stream API, som är helt inriktat på att hantera String-objekt och buffra data inom systemet. Node.js känner igen fyra huvudkategorier av strömmar, nämligen skrivbara, läsbara, duplexa och transformativa varianter.

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

Modulen File System (fs) innehåller en klass WriteStream som gör det möjligt att skapa en skrivbar ström för att överföra data till en angiven destination. Genom att använda metoden fs.createWriteStream() kan man skapa en ny ström och ange önskad målväg med hjälp av den angivna parametern. Dessutom kan en valfri uppsättning konfigurationsval inkluderas vid behov.

 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();
})();

Den angivna koden importerar funktionen createWriteStream() , som används i en anonym pilfunktion för att skapa en filström som lägger till data i en angiven fil, i det här fallet “myFile.txt”.I den anonyma funktionen finns en inbäddad funktion med namnet writeData() , som ansvarar för att skriva information till den angivna filen.

Funktionen createWriteStream() använder en buffert för att skriva in en serie siffror (från 0 till 9 999) i den angivna utdatafilen. När skriptet körs genereras en fil i den aktuella katalogen och fylls med information enligt följande:

/sv/images/myfile-initial-data-1.jpg

Den aktuella sifferuppsättningen slutar på 2 915, men den borde innehålla siffror så höga som 9. Denna skillnad beror på att varje WriteStream använder en cache som behåller en förutbestämd mängd information vid varje givet tillfälle. För att få reda på standardvärdet för denna inställning måste man använda alternativet highWaterMark.

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

Om den ovan nämnda instruktionen infogas i den namnlösa funktionen kommer följande meddelande att genereras på kommandotolken, enligt följande:

/sv/images/writestream-highwatermark-1.jpg

Det visade resultatet från terminalen visar att den förkonfigurerade highWaterMark-gränsen är inställd på 16 384 byte som standard. Det innebär att buffertens kapacitet begränsas till att rymma högst 16 384 byte information samtidigt. Följaktligen omfattar de första 2 915 tecknen (inklusive eventuella kommatecken eller mellanslag) gränsen för data som kan lagras i bufferten på en gång.

För att lösa problemet med buffertfel rekommenderas att man använder en strömhändelse. Strömmar genomgår flera händelser under dataöverföringsprocessen som inträffar vid olika tidpunkter. Bland dessa händelser är dräneringshändelsen särskilt väl lämpad för att hantera situationer där buffertfel uppstår.

I implementeringen av funktionen writeData() returnerar anropet av metoden write() för objektet WriteStream en booleska som anger om den tilldelade dataposten eller interna bufferten har nått sin förutbestämda tröskel, känd som “high water mark”, eller inte. Om detta villkor är uppfyllt betyder det att applikationen kan överföra ytterligare data till den associerade utdataströmmen. Omvänt, när metoden write() returnerar ett falskt värde, fortsätter kontrollflödet att tömma all återstående data i bufferten eftersom ytterligare skrivning inte kan utföras förrän bufferten har tömts.

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

Genom att införliva den ovannämnda händelsekoden för tömning i en anonym funktion kan WriteStreams buffert tömmas när den har nått sin maximala kapacitet.Följaktligen utlöser detta återkallandet av writeData()-metoden för att möjliggöra ytterligare dataöverföring. Vid exekvering av det modifierade programmet erhålls följande resultat:

/sv/images/the-writestream-drain-event-1.jpg

Det är viktigt att ta hänsyn till att programmet var tvunget att rensa WriteStream-bufferten vid tre olika tillfällen under hela dess drift. Dessutom verkar det som om textfilen också genomgick vissa förändringar.

/sv/images/myfile-updated-data-1.jpg

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

För att påbörja processen med att läsa data, börja med att skapa en begriplig ström genom att använda funktionen fs.createReadStream() .

 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 använder metoden createReadStream() för att få åtkomst till filen “myFile.txt” som tidigare genererats av en tidigare koditeration. Denna speciella metod tar emot en filsökväg, som kan presenteras i antingen ett sträng-, buffert- eller URL-format, tillsammans med olika valfria parametrar som argument för bearbetning.

I samband med en anonym funktion som rör strömmar är det anmärkningsvärt att det finns flera betydande strömrelaterade förekomster. Trots detta kan man konstatera att det inte finns någon indikation på att händelsen “drain” har inträffat. Detta fenomen kan tillskrivas det faktum att en läsbar ström vanligtvis inte buffrar data förrän antingen funktionen “stream.push(chunk)” anropas eller händelsen “readable” används.

Utlösningen av open-händelsen sker när en fil öppnas för läsning av användaren. Genom att koppla datahändelsen till en i sig kontinuerlig ström, övergår strömmen till ett tillstånd där data kan överföras omedelbart när de är tillgängliga. Om den medföljande koden körs kommer följande utdata att genereras:

/sv/images/read-stream-terminal-output-1.jpg

Hur man skapar och använder en duplexström

En duplexström omfattar både skrivbar och läsbar kapacitet, vilket möjliggör samtidiga läs- och skrivoperationer genom samma instans. Ett illustrativt fall involverar användning av nätmodulen i samband med upprättandet av en TCP-socket.

En enkel metod för att illustrera egenskaperna hos en duplex kommunikationskanal är att utveckla en TCP-server (Transmission Control Protocol) och ett klientsystem som kan utbyta information.

The server.js File

 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);
}); 

The client.js File

 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.');
});

Både server- och klientskript använder sig av en läsbar och skrivbar ström för att underlätta kommunikationen och möjliggöra utbyte av data mellan dem. Serverprogrammet initieras först och börjar aktivt vänta på inkommande anslutningar. När klienten startas upprättar den en anslutning till servern genom att ange

/sv/images/tcp-server-and-client-terminal-output-1.jpg

När en anslutning upprättats underlättar klienten dataöverföringen genom att använda sin WriteStream för att skicka information till servern. Samtidigt registrerar servern de inkommande data i terminalen innan den använder sin egen WriteStream för att sända data tillbaka till klienten. Därefter loggar klienten de mottagna uppgifterna och fortsätter att utbyta mer information innan förbindelsen avslutas. Vid denna tidpunkt behåller servern ett öppet tillstånd för att kunna ta emot ytterligare anslutningar från klienter.

Så här skapar och använder du en transformström

zlib- och kryptografiska strömmar. Zlib-strömmar underlättar komprimering och efterföljande dekomprimering av textfiler under filöverföringar, medan kryptografiska strömmar möjliggör säker kommunikation genom krypterings- och dekrypteringsoperationer.

Applikationen 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 fungerar genom att ta det ursprungliga textdokumentet, komprimera det och arkivera det i den aktuella mappen, tack vare effektiviteten hos den läsbara strömmens pipe()-funktion. Elimineringen av buffertar genom stream pipeline-teknik underlättar denna procedur.

Innan data skrivs till skriptets skrivbara ström, genomgår de en mindre omdirigering genom komprimeringsprocessen som underlättas av zlib-bibliotekets createGzip()-metod. Denna metod genererar en komprimerad version av filen och returnerar en ny instans av ett Gzip-objekt, som därefter blir mottagare av skrivströmmen.

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);
})(); 

Den aktuella applikationen bygger vidare på det komprimerade dokumentet, och om man skulle undersöka den nyskapade filen med namnet “myFile2.txt” skulle man upptäcka att dess innehåll exakt motsvarar det ursprungliga dokumentets.

/sv/images/myfile2-data-1.jpg

Varför är strömmar viktiga?

Streams spelar en avgörande roll för att optimera dataöverföringen genom att underlätta sömlös kommunikation mellan klient- och serversystem, samtidigt som de stöder effektiv komprimering och överföring av stora filstorlekar.

Strömmar förbättrar avsevärt funktionaliteten i programmeringsspråk genom att förenkla processen för dataöverföring. Avsaknaden av stream-funktioner leder till ökad komplexitet i dataöverföringsoperationer, vilket kräver en högre grad av manuell inblandning från utvecklare, vilket kan leda till en ökning av fel och minskad prestanda.