Потоците в Node.js могат да бъдат сложни, но си струва да отделите време, за да ги разберете.
Ключови изводи
- Потоците в Node.js са основен инструмент за обработка и трансфер на данни, което ги прави идеални за приложения в реално време и управлявани от събития.
- За да създадете записваем поток в Node.js, можете да използвате функцията createWriteStream() на модула fs, която записва данни на конкретно място.
- Четими, записваеми, дуплексни и трансформиращи са четирите типа потоци в Node.js, всеки със собствен случай на използване и функционалност.
Потокът е основен инструмент за програмиране, който се занимава с потока от данни. В основата си потокът обикновено представлява последователно прехвърляне на байтове от една точка в друга. Официалната документация на Node.js дефинира потока като абстрактен интерфейс, който можете да използвате за работа с данни.
Прехвърлянето на данни на компютър или през мрежа е идеално използване на поток.
Потоци в Node.js
Потоците изиграха съществена роля за успеха на Node.js. Те са идеални за обработка на данни в реално време и приложения, управлявани от събития, две забележителни характеристики на средата за изпълнение на Node.js.
За да създадете нов поток в Node.js, ще трябва да използвате API за поток, който работи изключително с низове и Node.js буферни данни. Node.js има четири типа потоци: записваеми, четими, дуплексни и трансформиращи.
Как да създадете и използвате поток с възможност за запис
Поток с възможност за запис ви позволява да пишете или изпращате данни до определено място. Модулът fs (файлова система) има клас WriteStream, който можете да използвате, за да създадете нов поток с fs.createWriteStream() функция. Тази функция приема пътя до файла, в който искате да запишете данни, както и незадължителен масив от опции.
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();
})();
Този код импортира createWriteStream() функция, която функцията на анонимната стрелка след това използва за създаване на поток, който записва данни в myFile.txt. Анонимната функция съдържа вътрешна функция, наречена writeData() който записва данни.
The createWriteStream() функцията работи с буфер, за да запише колекция от числа (0–9 999) в целевия файл. Въпреки това, когато стартирате скрипта по-горе, той създава файл в същата директория, който съдържа следните данни:
Текущата колекция от числа завършва на 2915, но трябваше да включва числа до 9999. Това несъответствие възниква, защото всеки WriteStream използва буфер, който съхранява фиксирано количество данни наведнъж. За да научите каква е тази стойност по подразбиране, ще трябва да се консултирате с highWaterMark опция.
console.log("The highWaterMark value is: " +
myWriteStream.writableHighWaterMark + " bytes.");
Добавянето на реда с код по-горе към анонимната функция ще произведе следния изход в терминала:
Изходът на терминала показва, че по подразбиране highWaterMark стойност (която може да се персонализира) е 16 384 байта. Това означава, че можете да съхранявате само под 16 384 байта данни в този буфер наведнъж. И така, до число 2915 (плюс всички запетаи и интервали) представлява максималното количество данни, които буферът може да съхранява наведнъж.
Решението на грешката в буфера е да се използва поточно събитие. Поток среща различни събития на различни етапи от процеса на прехвърляне на данни. The източване събитие е подходящият вариант за тази ситуация.
В writeData() функция по-горе, извикването на Напиши() на WriteStream функцията връща true, ако частта от данни (или вътрешният буфер) е под highWaterMark стойност. Това показва, че приложението може да изпраща повече данни към потока. Въпреки това, веднага след като пиши() функцията връща false, цикълът прекъсва, защото трябва да източите буфера.
myWriteStream.on('drain', () => {
console.log("a drain has occurred...");
writeData();
});
Вмъкване на източване код на събитие по-горе в анонимната функция ще изпразни Буферът на WriteStream когато има капацитет. След това припомня writeData() метод, така че да може да продължи да записва данни. Изпълнението на актуализираното приложение ще доведе до следния резултат:
Трябва да имате предвид, че приложението трябваше да източи WriteStream буфер три пъти по време на изпълнението му. Текстовият файл също претърпя някои промени:
Как да създадете и използвате четим поток
За да прочетете данни, започнете със създаване на четим поток с помощта на 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.");
});
})();
Скриптът по-горе използва createReadStream() метод за достъп до файла, създаден от предишния код: myFile.txt. The createReadStream() функцията приема път към файла (който може да бъде под формата на низ, буфер или URL) и няколко незадължителни опции като аргументи.
В анонимната функция има няколко важни събития в потока. Въпреки това, няма следа от източване събитие. Това е така, защото четливият поток буферира данни само когато извикате stream.push (парче) функция или използвайте четлив събитие.
The отворен събитието се задейства, когато fs отвори файла, от който искате да четете. Когато прикачите данни събитие към имплицитно непрекъснат поток, това кара потока да премине в течащ режим. Това позволява на данните да преминат веднага щом станат налични. Изпълнението на горното приложение води до следния резултат:
Как да създадете и използвате дуплексен поток
Дуплексният поток реализира интерфейси на поток както за записване, така и за четене, така че можете да четете и пишете в такъв поток. Един пример е TCP сокет, който разчита на мрежовия модул за създаването си.
Лесен начин за демонстриране на свойствата на дуплексен поток е да се създаде TCP сървър и клиент, който прехвърля данни.
Файлът server.js
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);
});
Файлът 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.');
});
Ще забележите, че както сървърът, така и клиентският скрипт използват поток, който може да се чете и записва, за да комуникират (прехвърляне и получаване на данни). Естествено, първо се стартира сървърното приложение и започва да слуша за връзки. Веднага щом стартирате клиента, той се свързва със сървъра с помощта на номера на TCP порта.
След установяване на връзка, клиентът инициира прехвърляне на данни, като пише на сървъра, използвайки своя WriteStream. Сървърът регистрира данните, които получава към терминала, след което записва данни, използвайки своя WriteStream. Накрая клиентът регистрира данните, които получава, записва допълнителни данни и след това прекъсва връзката със сървъра. Сървърът остава отворен за свързване на други клиенти.
Как да създадете и използвате трансформиращ поток
Трансформиращите потоци са дуплексни потоци, в които изходът е свързан с, но различен от входа. Node.js има два типа потоци за трансформация: zlib и крипто потоци. Zlib поток може да компресира текстов файл и след това да го декомпресира след прехвърляне на файл.
Приложението 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);
})();
Този прост скрипт взема оригиналния текстов файл, компресира го и го съхранява в текущата директория. Това е лесен процес благодарение на четливия поток тръба() метод. Конвейерите на потоци премахват използването на буфери и тръбопроводи на данни директно от един поток в друг.
Въпреки това, преди данните да достигнат до записваемия поток в скрипта, е необходимо малко отклонение чрез метода createGzip() на zlib. Този метод компресира файла и връща нов Gzip обект, който след това получава потокът за запис.
Приложението 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);
})();
Този скрипт по-горе взема компресирания файл и го декомпресира. Ако отворите новия myFile2.txt файл, ще видите, че той съдържа същите данни като оригиналния файл:
Защо потоците са важни?
Потоците подобряват ефективността на трансфера на данни. Четимите и записваеми потоци служат като основа, която позволява комуникация между клиенти и сървъри, както и компресиране и прехвърляне на големи файлове.
Потоците също подобряват производителността на езиците за програмиране. Без потоци процесът на прехвърляне на данни става по-сложен, изисквайки по-голямо ръчно въвеждане от разработчиците и водещо до повече грешки и проблеми с производителността.