Dans le chapitre précédent nous avons vu comment lire un fichier à l'aide de la méthode fs.readFile()
. Cette méthode convient très bien pour la lecture de fichiers de petite taille mais peut s'avérer problématique pour de gros fichiers car elle oblige NodeJS à stocker le résultat de la lecture dans une variable. Si on essaie alors de lire des fichiers plus volumineux on peut alors très rapidement rencontré des problèmes.
Les streams
Un stream, permet de lire et d'écrire un contenu sous forme de flux et de recevoir les informations sous forme de petits blocs plus digestes pour la mémoire. Ces streams peuvent être de plusieurs nature :
- Readable, pour un flux qui ne fait que lire des informations (comme par exemple
fs.createReadStream()
) - Writable, pour un flux qui écrit les données qu'il reçoit (
fs.createWriteStream()
) - Duplex, pour un flux qui est à la fois capable de lire et d'écrire
- Transform, pour un flux qui peut lire et transformer les information lors de la lecture ou de l'écriture (par exemple
zlib.createDeflate()
)
Comme pour le reste des objets de NodeJS ces flux utilisent le modèle d'évènement pour émettre des évènement lorsque certaines actions sont atteintes. Par exemple, un readable stream possèdera un évènement data
qui permettra de suivre l'état de lecture du fichier.
let fs = require('fs')
let file = 'demo.mp4'
fs.stat(file, (err, stat) => {
let total = stat.size
let progress = 0
let read = fs.createReadStream(file)
read.on('data', (chunk) => {
progress += chunk.length
console.log("J'ai lu " + Math.round(100 * progress / total) + "%")
})
})
Les flux d'écriture possèdent quand à eux une méthode write()
qui permet d'écrire les données. Cette méthode peut renvoyer false
pour indiquer que l'écriture n'est pas disponible et que le flux doit être mis en pause jusqu'à que l'évènement drain
soit renvoyé.
function writeOneMillionTimes(writer, data, encoding, callback) {
let i = 1000000
write()
function write() {
var ok = true // Permet de savoir si le flux peut accepter de nouvelles écritures
while (i > 0 && ok) {
i--
if (i === 0) {
// on écrit le denier morceau, on passe donc le callback en 3ème paramètre
writer.write(data, encoding, callback)
} else {
// on écrit dans le stream et on stocke le succès ou l'échec de l'écriture
ok = writer.write(data, encoding)
}
}
if (i > 0) {
// On ne déclenche une nouvelle écriture que lorsque le flux est prêt
writer.once('drain', write)
}
}
}
Rassurez vous, NodeJS intègre une méthode permettant de relier des flux entre eux afin d'éviter d'avoir à tout cabler manuellement. Que se passe-t-il si on veut copier un fichier par exemple ? Les flux de lecture possèdent une méthode pipe()
qui permet d'attacher un flux d'écriture. Le flux de données sera automatiquement géré afin que le flux d'écriture ne soit pas submerger par une lecture trop rapide des informations.
let fs = require('fs')
let file = 'demo.mp4'
fs.stat(file, (err, stat) => {
let total = stat.size
let progress = 0
let read = fs.createReadStream(file)
let write = fs.createWriteStream('copy.mp4')
read.on('data', (chunk) => {
progress += chunk.length
console.log("J'ai lu " + Math.round(100 * progress / total) + "%")
})
// Un simple pipe suffit pour brancher notre lecture à notre écriture, NodeJS se charge du reste :)
read.pipe(write)
write.on('finish', () => {
console.log('Le fichier a bien été copié')
})
})