159 lines
8.3 KiB
Markdown
159 lines
8.3 KiB
Markdown
|
---
|
|||
|
title: Streams
|
|||
|
localeTitle: Streams
|
|||
|
---
|
|||
|
## Streams
|
|||
|
|
|||
|
Потоки доступны в базовом API Node.js как объекты, которые позволяют считывать или записывать данные непрерывным образом. В принципе, поток делает это в кусках по сравнению с буфером, который выполняет бит за бит, тем самым делая его медленным процессом.
|
|||
|
|
|||
|
Доступны четыре типа потоков:
|
|||
|
|
|||
|
* Чтение (потоки, из которых считываются данные)
|
|||
|
* Writable (потоки, на которые записаны данные)
|
|||
|
* Дуплекс (потоки, которые являются читаемыми и записываемыми)
|
|||
|
* Трансформация (Дуплексные потоки, которые могут изменять данные по мере их чтения и записи)
|
|||
|
|
|||
|
У каждого доступного типа есть несколько методов. Некоторые из них:
|
|||
|
|
|||
|
* данных (это выполняется, когда доступны данные)
|
|||
|
* end (это срабатывает, когда нет данных, оставшихся для чтения)
|
|||
|
* ошибка (это выполняется, когда есть ошибка приема или записи данных)
|
|||
|
|
|||
|
### труба
|
|||
|
|
|||
|
В программировании концепция `pipe` не нова. Системы на основе Unix прагматично использовали его с 1970-х годов. Что делает труба? `pipe` обычно соединяет источник и пункт назначения. Он передает выход одной функции в качестве входа другой функции.
|
|||
|
|
|||
|
В Node.js `pipe` используется одинаково, для сопряжения входов и выходов различных операций. `pipe()` доступна как функция, которая берет читаемый поток источника и присоединяет вывод к потоку назначения. Общий синтаксис может быть представлен как:
|
|||
|
|
|||
|
```javascript
|
|||
|
src.pipe(dest);
|
|||
|
```
|
|||
|
|
|||
|
Функции нескольких `pipe()` также могут быть соединены вместе.
|
|||
|
|
|||
|
```javascript
|
|||
|
a.pipe(b).pipe(c);
|
|||
|
|
|||
|
// which is equivalent to
|
|||
|
|
|||
|
a.pipe(b);
|
|||
|
b.pipe(c);
|
|||
|
```
|
|||
|
|
|||
|
### Чтение потоков
|
|||
|
|
|||
|
Потоки, которые создают данные, которые могут быть присоединены как входные данные к записываемому потоку, называются Readable stream. Чтобы создать читаемый поток:
|
|||
|
|
|||
|
```javascript
|
|||
|
const { Readable } = require('stream');
|
|||
|
|
|||
|
const readable = new Readable();
|
|||
|
|
|||
|
readable.on('data', chunk => {
|
|||
|
console.log(`Received ${chunk.length} bytes of data.`);
|
|||
|
});
|
|||
|
readable.on('end', () => {
|
|||
|
console.log('There will be no more data.');
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
### Считываемый поток
|
|||
|
|
|||
|
Это тип потока, который можно `pipe()` данные из читаемого источника. Чтобы создать поток, доступный для записи, мы используем конструкторский подход. Мы создаем объект из него и передаем несколько параметров. Метод принимает три аргумента:
|
|||
|
|
|||
|
* кусок: буфер
|
|||
|
* кодирование: преобразование данных в удобочитаемую форму
|
|||
|
* callback: функция, которая вызывается, когда данные обрабатываются из блока
|
|||
|
|
|||
|
```javascript
|
|||
|
const { Writable } = require('stream');
|
|||
|
const writable = new Writable({
|
|||
|
write(chunk, encoding, callback) {
|
|||
|
console.log(chunk.toString());
|
|||
|
callback();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
process.stdin.pipe(writable);
|
|||
|
```
|
|||
|
|
|||
|
### Дуплексные потоки
|
|||
|
|
|||
|
Дуплексные потоки помогают одновременно реализовать как считываемые, так и записываемые потоки.
|
|||
|
|
|||
|
```javascript
|
|||
|
const { Duplex } = require('stream');
|
|||
|
|
|||
|
const inoutStream = new Duplex({
|
|||
|
write(chunk, encoding, callback) {
|
|||
|
console.log(chunk.toString());
|
|||
|
callback();
|
|||
|
},
|
|||
|
|
|||
|
read(size) {
|
|||
|
this.push(String.fromCharCode(this.currentCharCode++));
|
|||
|
if (this.currentCharCode > 90) {
|
|||
|
this.push(null);
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
inoutStream.currentCharCode = 65;
|
|||
|
process.stdin.pipe(inoutStream).pipe(process.stdout);
|
|||
|
```
|
|||
|
|
|||
|
Поток `stdin` передает считываемые данные в дуплексный поток. Эта `stdout` помогает нам видеть данные. Читаемые и записываемые части дуплексного потока полностью независимы друг от друга.
|
|||
|
|
|||
|
### Преобразовать поток
|
|||
|
|
|||
|
Этот тип потока представляет собой более сложную версию дуплексного потока.
|
|||
|
|
|||
|
```javascript
|
|||
|
const { Transform } = require('stream');
|
|||
|
|
|||
|
const upperCaseTr = new Transform({
|
|||
|
transform(chunk, encoding, callback) {
|
|||
|
this.push(chunk.toString().toUpperCase());
|
|||
|
callback();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
process.stdin.pipe(upperCaseTr).pipe(process.stdout);
|
|||
|
```
|
|||
|
|
|||
|
Данные, которые мы потребляем, такие же, как в предыдущем примере дуплексного потока. Дело в том, что `transform()` не требует реализации методов `read` или `write` . Он объединяет оба метода.
|
|||
|
|
|||
|
### Зачем использовать потоки?
|
|||
|
|
|||
|
Поскольку Node.js является асинхронным, поэтому он взаимодействует, передавая обратные вызовы функциям с диском и сетью. Приведенный ниже пример читает данные из файла на диске и отвечает на него по сетевому запросу от клиента.
|
|||
|
|
|||
|
```javascript
|
|||
|
const http = require('http');
|
|||
|
const fs = require('fs');
|
|||
|
|
|||
|
const server = http.createServer((req, res) => {
|
|||
|
fs.readFile('data.txt', (err, data) => {
|
|||
|
res.end(data);
|
|||
|
});
|
|||
|
});
|
|||
|
server.listen(8000);
|
|||
|
```
|
|||
|
|
|||
|
Вышеприведенный фрагмент кода будет работать, но все данные из файла сначала войдут в память для каждого запроса, прежде чем записывать результат обратно на запрос клиента. Если файл, который мы читаем, слишком велик, это может стать очень тяжелым и дорогостоящим вызовом сервера, так как он будет потреблять много памяти для продвижения процесса. Пользовательский опыт на стороне клиента также будет страдать от задержки.
|
|||
|
|
|||
|
В этом случае, если мы будем использовать потоки, данные будут отправляться на запрос клиента как один фрагмент за раз, как только они будут получены с диска.
|
|||
|
|
|||
|
```javascript
|
|||
|
const http = require('http');
|
|||
|
const fs = require('fs');
|
|||
|
|
|||
|
const server = http.createServer((req, res) => {
|
|||
|
const stream = fs.createReadStream('data.txt');
|
|||
|
stream.pipe(res);
|
|||
|
});
|
|||
|
server.listen(8000);
|
|||
|
```
|
|||
|
|
|||
|
Здесь `pipe()` заботится о записи или в нашем случае, отправляя данные с объектом ответа и как только все данные считываются из файла, чтобы закрыть соединение.
|
|||
|
|
|||
|
Примечание. `process.stdin` и `process.stdout` строятся в потоках в глобальном объекте `process` предоставляемом Node.js API.
|