Node.js
Utiliser des flux
Recherche…
Paramètres
Paramètre | Définition |
---|---|
Flux lisible | type de flux où les données peuvent être lues |
Courant inscriptible | type de flux où les données peuvent être écrites |
Flux duplex | type de flux à la fois lisible et inscriptible |
Transformer le flux | type de flux duplex pouvant transformer les données en cours de lecture puis d'écriture |
Lire des données depuis TextFile avec des flux
Les E / S dans le nœud sont asynchrones. Ainsi, interagir avec le disque et le réseau implique de transmettre des rappels aux fonctions. Vous pourriez être tenté d'écrire du code qui sert un fichier à partir du disque comme ceci:
var http = require('http');
var fs = require('fs');
var server = http.createServer(function (req, res) {
fs.readFile(__dirname + '/data.txt', function (err, data) {
res.end(data);
});
});
server.listen(8000);
Ce code fonctionne mais il est encombrant et stocke en mémoire tout le fichier data.txt pour chaque requête avant de renvoyer le résultat aux clients. Si le fichier data.txt est très volumineux, votre programme pourrait commencer à consommer beaucoup de mémoire car il est utilisé simultanément par de nombreux utilisateurs, en particulier pour les utilisateurs dont les connexions sont lentes.
L'expérience utilisateur est également médiocre car les utilisateurs devront attendre que tout le fichier soit mis en mémoire tampon sur votre serveur avant de pouvoir recevoir du contenu.
Heureusement, les deux arguments (req, res) sont des flux, ce qui signifie que nous pouvons écrire cela beaucoup mieux en utilisant fs.createReadStream () au lieu de fs.readFile ():
var http = require('http');
var fs = require('fs');
var server = http.createServer(function (req, res) {
var stream = fs.createReadStream(__dirname + '/data.txt');
stream.pipe(res);
});
server.listen(8000);
Ici, .pipe () prend en charge l'écoute des événements 'data' et 'end' du fichier fs.createReadStream (). Ce code n'est pas seulement plus propre, mais maintenant, le fichier data.txt sera écrit sur les clients, un morceau à la fois, dès qu'ils sont reçus du disque.
Cours d'eau
Les flux lisibles peuvent être "canalisés" ou connectés à des flux inscriptibles. Cela rend le flux de données du flux source vers le flux de destination sans trop d'effort.
var fs = require('fs')
var readable = fs.createReadStream('file1.txt')
var writable = fs.createWriteStream('file2.txt')
readable.pipe(writable) // returns writable
Lorsque des flux inscriptibles sont également des flux lisibles, c'est-à-dire lorsqu'ils sont des flux duplex , vous pouvez continuer à les transmettre à d'autres flux inscriptibles.
var zlib = require('zlib')
fs.createReadStream('style.css')
.pipe(zlib.createGzip()) // The returned object, zlib.Gzip, is a duplex stream.
.pipe(fs.createWriteStream('style.css.gz')
Les flux lisibles peuvent également être acheminés vers plusieurs flux.
var readable = fs.createReadStream('source.css')
readable.pipe(zlib.createGzip()).pipe(fs.createWriteStream('output.css.gz'))
readable.pipe(fs.createWriteStream('output.css')
Notez que vous devez accéder aux flux de sortie de manière synchrone (en même temps) avant tout flux de données. Sinon, des données incomplètes pourraient être transmises.
Notez également que les objets de flux peuvent émettre error
événements d' error
; Assurez-vous de gérer ces événements de manière responsable sur chaque flux, selon vos besoins:
var readable = fs.createReadStream('file3.txt')
var writable = fs.createWriteStream('file4.txt')
readable.pipe(writable)
readable.on('error', console.error)
writable.on('error', console.error)
Création de votre propre flux lisible / inscriptible
Nous verrons les objets de flux renvoyés par des modules tels que fs, etc., mais si nous voulons créer notre propre objet pouvant être diffusé.
Pour créer un objet Stream, nous devons utiliser le module de flux fourni par NodeJs
var fs = require("fs");
var stream = require("stream").Writable;
/*
* Implementing the write function in writable stream class.
* This is the function which will be used when other stream is piped into this
* writable stream.
*/
stream.prototype._write = function(chunk, data){
console.log(data);
}
var customStream = new stream();
fs.createReadStream("am1.js").pipe(customStream);
Cela nous donnera notre propre flux accessible en écriture. nous pouvons implémenter n'importe quoi dans la fonction _write . La méthode ci-dessus fonctionne dans la version NodeJs 4.xx mais dans NodeJs 6.x ES6 introduit des classes, donc la syntaxe a changé. Voici le code pour la version 6.x de NodeJs
const Writable = require('stream').Writable;
class MyWritable extends Writable {
constructor(options) {
super(options);
}
_write(chunk, encoding, callback) {
console.log(chunk);
}
}
Pourquoi Streams?
Examinons les deux exemples suivants pour lire le contenu d'un fichier:
Le premier, qui utilise une méthode asynchrone pour lire un fichier et fournit une fonction de rappel qui est appelée une fois que le fichier est entièrement lu dans la mémoire:
fs.readFile(`${__dirname}/utils.js`, (err, data) => {
if (err) {
handleError(err);
} else {
console.log(data.toString());
}
})
Et le second, qui utilise des streams
pour lire le contenu du fichier, pièce par pièce:
var fileStream = fs.createReadStream(`${__dirname}/file`);
var fileContent = '';
fileStream.on('data', data => {
fileContent += data.toString();
})
fileStream.on('end', () => {
console.log(fileContent);
})
fileStream.on('error', err => {
handleError(err)
})
Il convient de mentionner que les deux exemples font exactement la même chose . Quelle est la différence alors?
- Le premier est plus court et semble plus élégant
- La seconde vous permet de faire un traitement sur le fichier en cours de lecture (!)
Lorsque les fichiers que vous traitez sont petits, il n'y a pas d'effet réel lors de l'utilisation de streams
, mais que se passe-t-il lorsque le fichier est volumineux? (si grand qu'il faut 10 secondes pour le lire en mémoire)
Sans streams
vous attendez, ne faites absolument rien (à moins que votre processus ne fasse autre chose), jusqu'à ce que les 10 secondes passent et que le fichier soit entièrement lu , et alors seulement vous pouvez commencer à traiter le fichier.
Avec les streams
, vous obtenez le contenu du fichier pièce par pièce, exactement quand ils sont disponibles - et cela vous permet de traiter le fichier pendant sa lecture.
L'exemple ci-dessus n'illustre pas la manière dont les streams
peuvent être utilisés pour des tâches qui ne peuvent pas être effectuées lors du rappel, alors examinons un autre exemple:
Je voudrais télécharger un fichier gzip
, le décompresser et enregistrer son contenu sur le disque. Étant donné l' url
du fichier, c'est ce qui doit être fait:
- Télécharger le fichier
- Décompressez le fichier
- Enregistrez-le sur le disque
Voici un [petit fichier] [1], stocké dans mon stockage S3
. Le code suivant effectue les opérations ci-dessus dans le mode de rappel.
var startTime = Date.now()
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}, (err, data) => {
// here, the whole file was downloaded
zlib.gunzip(data.Body, (err, data) => {
// here, the whole file was unzipped
fs.writeFile(`${__dirname}/tweets.json`, data, err => {
if (err) console.error(err)
// here, the whole file was written to disk
var endTime = Date.now()
console.log(`${endTime - startTime} milliseconds`) // 1339 milliseconds
})
})
})
// 1339 milliseconds
Voici à quoi cela ressemble en utilisant des streams
:
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}).createReadStream()
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream(`${__dirname}/tweets.json`));
// 1204 milliseconds
Oui, ce n'est pas plus rapide lorsque vous traitez de petits fichiers - le fichier testé pèse 80KB
. Tester cela sur un fichier plus gros, de 71MB
gzippé ( 71MB
Mo 382MB
), montre que la version des streams
est beaucoup plus rapide
- Il a fallu 20925 millisecondes pour télécharger
71MB
, décompressez-le, puis écrivez382MB
sur le disque - en utilisant le mode de rappel . - En comparaison, il a fallu 13434 millisecondes pour faire la même chose en utilisant la version de
streams
(35% plus rapide, pour un fichier pas si gros).