J'écris un fichier volumineux avec node.js en utilisant un flux enregistrable :
var fs = require('fs');
var stream = fs.createWriteStream('someFile.txt', { flags : 'w' });
var lines;
while (lines = getLines()) {
for (var i = 0; i < lines.length; i++) {
stream.write( lines[i] );
}
}
Je me demande si ce schéma est sûr sans utiliser drain
event? Si ce n’est pas le cas (ce qui, selon moi, est le cas), quel est le modèle utilisé pour écrire des données volumineuses arbitraires dans un fichier?
C'est comme ça que je l'ai finalement fait. L'idée est de créer un flux lisible implémentant ReadStream interface, puis d'utiliser la méthode pipe()
pour diriger les données vers un flux inscriptible.
var fs = require('fs');
var writeStream = fs.createWriteStream('someFile.txt', { flags : 'w' });
var readStream = new MyReadStream();
readStream.pipe(writeStream);
writeStream.on('close', function () {
console.log('All done!');
});
L'exemple de la classe MyReadStream
peut être tiré de mongoose QueryStream .
L'idée derrière drain est que vous l'utiliseriez pour tester ici:
var fs = require('fs');
var stream = fs.createWriteStream('someFile.txt', {flags: 'w'});
var lines;
while (lines = getLines()) {
for (var i = 0; i < lines.length; i++) {
stream.write(lines[i]); //<-- the place to test
}
}
que vous n'êtes pas. Il faudrait donc rechercher un nouveau résultat pour le rendre "réentrant".
var fs = require('fs');
var stream = fs.createWriteStream('someFile.txt', {flags: 'w'});
var lines;
while (lines = getLines()) {
for (var i = 0; i < lines.length; i++) {
var written = stream.write(lines[i]); //<-- the place to test
if (!written){
//do something here to wait till you can safely write again
//this means prepare a buffer and wait till you can come back to finish
// lines[i] -> remainder
}
}
}
Cependant, cela signifie-t-il que vous devez continuer à mettre en mémoire tampon getLines pendant que vous attendez?
var fs = require('fs');
var stream = fs.createWriteStream('someFile.txt', {flags: 'w'});
var lines,
buffer = {
remainingLines = []
};
while (lines = getLines()) {
for (var i = 0; i < lines.length; i++) {
var written = stream.write(lines[i]); //<-- the place to test
if (!written){
//do something here to wait till you can safely write again
//this means prepare a buffer and wait till you can come back to finish
// lines[i] -> remainder
buffer.remainingLines = lines.slice(i);
break;
//notice there's no way to re-run this once we leave here.
}
}
}
stream.on('drain',function(){
if (buffer.remainingLines.length){
for (var i = 0; i < buffer.remainingLines.length; i++) {
var written = stream.write(buffer.remainingLines[i]); //<-- the place to test
if (!written){
//do something here to wait till you can safely write again
//this means prepare a buffer and wait till you can come back to finish
// lines[i] -> remainder
buffer.remainingLines = lines.slice(i);
}
}
}
});
[Edit] La version mise à jour de Node.js writable.write(...)
API say:
[La] valeur de retour est strictement consultative. Vous POUVEZ continuer à écrire, même si cela retourne faux. Cependant, les écritures seront mises en mémoire tampon, il est donc préférable de ne pas le faire excessivement. Attendez plutôt l'événement de drain avant d'écrire plus de données.
[Original] Extrait de la stream.write(...)
documentation (c'est moi qui souligne):
Renvoie
true
si la chaîne a été vidée dans la mémoire tampon du noyau. Renvoiefalse
pour indiquer que la mémoire tampon du noyau est pleine et que les données seront envoyées ultérieurement.
J'interprète cela comme signifiant que la fonction "write" renvoie true
si la chaîne donnée a été immédiatement écrite dans le tampon sous-jacent du système d'exploitation ou false
si elle n'a pas encore été écrite mais sera écrit par la fonction write} (par exemple was vraisemblablement mis en mémoire tampon par WriteStream) afin que vous n'ayez pas à appeler "write" à nouveau.
J'ai trouvé que les flux étaient un moyen peu performant de gérer des fichiers volumineux - c'est parce que vous ne pouvez pas définir une taille de tampon d'entrée adéquate (au moins, je ne connais pas de bonne façon de le faire). C'est ce que je fais:
var fs = require('fs');
var i = fs.openSync('input.txt', 'r');
var o = fs.openSync('output.txt', 'w');
var buf = new Buffer(1024 * 1024), len, prev = '';
while(len = fs.readSync(i, buf, 0, buf.length)) {
var a = (prev + buf.toString('ascii', 0, len)).split('\n');
prev = len === buf.length ? '\n' + a.splice(a.length - 1)[0] : '';
var out = '';
a.forEach(function(line) {
if(!line)
return;
// do something with your line here
out += line + '\n';
});
var bout = new Buffer(out, 'ascii');
fs.writeSync(o, bout, 0, bout.length);
}
fs.closeSync(o);
fs.closeSync(i);
Plusieurs réponses suggérées à cette question ont complètement manqué le point sur les flux.
Ce module peut aider https://www.npmjs.org/package/JSONStream
Cependant, supposons la situation décrite et écrivons le code nous-mêmes. Vous lisez depuis MongoDB en tant que flux, avec ObjectMode = true par défaut.
Cela entraînera des problèmes si vous essayez de diffuser directement dans un fichier, par exemple une erreur du type "Invalid non-string/buffer chunk".
La solution à ce type de problème est très simple.
Il suffit de mettre une autre transformation entre lisible et inscriptible pour adapter l'objet lisible à une chaîne inscriptible de manière appropriée.
Exemple de solution de code:
var fs = require('fs'),
writeStream = fs.createWriteStream('./out' + process.pid, {flags: 'w', encoding: 'utf-8' }),
stream = require('stream'),
stringifier = new stream.Transform();
stringifier._writableState.objectMode = true;
stringifier._transform = function (data, encoding, done) {
this.Push(JSON.stringify(data));
this.Push('\n');
done();
}
rowFeedDao.getRowFeedsStream(merchantId, jobId)
.pipe(stringifier)
.pipe(writeStream).on('error', function (err) {
// handle error condition
}
La façon la plus propre de gérer cela est de faire de votre générateur de ligne un flux lisible - appelons-le lineReader
. Ensuite, ce qui suit gérera automatiquement les tampons et les drainera bien pour vous:
lineReader.pipe(fs.createWriteStream('someFile.txt'));
Si vous ne souhaitez pas créer de flux lisible, vous pouvez écouter la sortie de write
pour connaître la saturation du tampon et répondre comme suit:
var i = 0, n = lines.length;
function write () {
if (i === n) return; // A callback could go here to know when it's done.
while (stream.write(lines[i++]) && i < n);
stream.once('drain', write);
}
write(); // Initial call.
Vous trouverez un exemple plus long de cette situation ici .
Si vous ne disposez pas d'un flux d'entrée, vous ne pouvez pas facilement utiliser pipe . Aucune de ce qui précède n'a fonctionné pour moi, l'événement drain ne se déclenche pas. Résolu comme suit (basé sur la réponse de Tylers):
var lines[]; // some very large array
var i = 0;
function write() {
if (i < lines.length) {
wstream.write(lines[i]), function(err){
if (err) {
console.log(err);
} else {
i++;
write();
}
});
} else {
wstream.end();
console.log("done");
}
};
write();