Comment lire le contenu d'un flux Node.js dans une variable chaîne?

113

Je pirate un programme Node qui utilise smtp-protocolpour capturer les e-mails SMTP et agir sur les données de messagerie. La bibliothèque fournit les données de messagerie sous forme de flux, et je ne sais pas comment les intégrer dans une chaîne.

Je l'écris actuellement sur stdout avec stream.pipe(process.stdout, { end: false }), mais comme je l'ai dit, j'ai besoin des données de flux dans une chaîne à la place, que je peux utiliser une fois le flux terminé.

Comment collecter toutes les données d'un flux Node.js dans une chaîne?

obrienmd
la source
Vous devez copier le flux ou le marquer avec (autoClose: false). C'est une mauvaise pratique de polluer la mémoire.
19h

Réponses:

41

(Cette réponse date d'il y a des années, quand c'était la meilleure réponse. Il y a maintenant une meilleure réponse en dessous de celle-ci. Je n'ai pas suivi le node.js, et je ne peux pas supprimer cette réponse car elle est marquée "correct sur cette question ". Si vous envisagez de cliquer, que voulez-vous que je fasse?)

La clé est d'utiliser les événements dataet endd'un flux lisible . Écoutez ces événements:

stream.on('data', (chunk) => { ... });
stream.on('end', () => { ... });

Lorsque vous recevez l' dataévénement, ajoutez le nouveau bloc de données à un tampon créé pour collecter les données.

Lorsque vous recevez l' endévénement, convertissez le tampon terminé en une chaîne, si nécessaire. Ensuite, faites ce que vous devez en faire.

ControlAltDel
la source
149
Quelques lignes de code illustrant la réponse sont préférables au simple pointage d'un lien vers l'API. Ne soyez pas en désaccord avec la réponse, ne croyez pas qu'elle est suffisamment complète.
arcseldon le
3
Avec les versions plus récentes de node.js, c'est plus propre: stackoverflow.com/a/35530615/271961
Simon A. Eugster
La réponse doit être mise à jour pour ne pas recommander d'utiliser une bibliothèque de promesses, mais utiliser des promesses natives.
Dan Dascalescu
@DanDascalescu Je suis d'accord avec vous. Le problème est que j'ai écrit cette réponse il y a 7 ans et que je n'ai pas suivi node.js. Si vous êtes quelqu'un d'autre et souhaitez le mettre à jour, ce serait génial. Ou je pourrais simplement le supprimer, car il semble déjà y avoir une meilleure réponse. Que recommanderais-tu?
ControlAltDel
@ControlAltDel: J'apprécie votre initiative de supprimer une réponse qui n'est plus la meilleure. J'aimerais que les autres aient une discipline similaire .
Dan Dascalescu
129

Une autre façon serait de convertir le flux en une promesse (reportez-vous à l'exemple ci-dessous) et d'utiliser then(ou await) pour affecter la valeur résolue à une variable.

function streamToString (stream) {
  const chunks = []
  return new Promise((resolve, reject) => {
    stream.on('data', chunk => chunks.push(chunk))
    stream.on('error', reject)
    stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
  })
}

const result = await streamToString(stream)
Marlon Bernardes
la source
Je suis vraiment nouveau flux et promesses et je reçois cette erreur: SyntaxError: await is only valid in async function. Qu'est-ce que je fais mal?
JohnK
Vous devez appeler la fonction streamtostring dans une fonction asynchrone. Pour éviter cela, vous pouvez également fairestreamToString(stream).then(function(response){//Do whatever you want with response});
Enclo Creations
23
Cela devrait être la meilleure réponse. Félicitations pour la production de la seule solution qui fait tout bien, avec (1) stocker les morceaux en tant que tampons et n'appeler qu'à .toString("utf8")la fin, pour éviter le problème d'un échec de décodage si un morceau est divisé au milieu d'un caractère multi-octets; (2) la gestion des erreurs réelles; (3) mettre le code dans une fonction, afin qu'il puisse être réutilisé et non copié; (4) en utilisant des promesses pour que la fonction puisse être awaitactivée; (5) petit code qui ne traîne pas dans un million de dépendances, contrairement à certaines bibliothèques npm; (6) Syntaxe ES6 et meilleures pratiques modernes.
MultiplyByZer0
Pourquoi ne pas déplacer le tableau des morceaux dans la promesse?
Jenny O'Reilly
1
Après avoir trouvé essentiellement le même code en utilisant la réponse principale actuelle comme indice, j'ai remarqué que le code ci-dessus pouvait échouer Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringsi le flux produisait des stringmorceaux au lieu de Buffer. L'utilisation chunks.push(Buffer.from(chunk))devrait fonctionner avec les deux stringet les Buffermorceaux.
Andrei LED
67

Aucun de ces éléments n'a fonctionné pour moi. J'avais besoin d'utiliser l'objet Buffer:

  const chunks = [];

  readStream.on("data", function (chunk) {
    chunks.push(chunk);
  });

  // Send the buffer or you can put it into a var
  readStream.on("end", function () {
    res.send(Buffer.concat(chunks));
  });
Ricky
la source
7
c'est en fait la façon la plus propre de le faire;)
Ivo
7
Fonctionne très bien. Juste une note: si vous voulez un type de chaîne approprié, vous devrez appeler .toString () sur l'objet Buffer résultant de l'appel concat ()
Bryan Johnson
64

J'espère que cela est plus utile que la réponse ci-dessus:

var string = '';
stream.on('data',function(data){
  string += data.toString();
  console.log('stream data ' + part);
});

stream.on('end',function(){
  console.log('final output ' + string);
});

Notez que la concaténation de chaînes n'est pas le moyen le plus efficace de collecter les parties de chaîne, mais elle est utilisée pour plus de simplicité (et peut-être que votre code ne se soucie pas de l'efficacité).

En outre, ce code peut produire des échecs imprévisibles pour le texte non ASCII (il suppose que chaque caractère tient dans un octet), mais peut-être que vous ne vous en souciez pas non plus.

Tom Carchrae
la source
4
Quel serait un moyen plus efficace de collecter des parties de cordes? TY
sean2078
2
vous pouvez utiliser un tampon docs.nodejitsu.com/articles/advanced/buffers/how-to-use-buffers mais cela dépend vraiment de votre utilisation.
Tom Carchrae
2
Utilisez un tableau de chaînes dans lequel vous ajoutez chaque nouveau bloc au tableau et appelez join("")le tableau à la fin.
Valeriu Paloş
14
Ce n'est pas juste. Si le tampon est à mi-chemin d'un point de code multi-octets, le toString () recevra un utf-8 malformé et vous vous retrouverez avec un tas de dans votre chaîne.
alextgordon
2
@alextgordon a raison. Dans de très rares cas, lorsque j'avais beaucoup de morceaux, je les ai reçus au début et à la fin des morceaux. Surtout quand il y a des symboles russes sur les bords. Il est donc correct de concaténer des morceaux et de les convertir à la fin au lieu de les convertir et de les concaténer. Dans mon cas, la demande a été faite d'un service à un autre avec request.js avec encodage par défaut
Mike Yermolayev
21

J'utilise généralement cette fonction simple pour transformer un flux en une chaîne:

function streamToString(stream, cb) {
  const chunks = [];
  stream.on('data', (chunk) => {
    chunks.push(chunk.toString());
  });
  stream.on('end', () => {
    cb(chunks.join(''));
  });
}

Exemple d'utilisation:

let stream = fs.createReadStream('./myFile.foo');
streamToString(stream, (data) => {
  console.log(data);  // data is now my string variable
});
dreampulse
la source
1
Réponse utile mais il semble que chaque morceau doit être converti en chaîne avant d'être poussé dans le tableau:chunks.push(chunk.toString());
Nicolas Le Thierry d'Ennequin
1
C'est le seul qui a fonctionné pour moi! Grand merci
538ROMEO
1
C'était une excellente réponse!
Aft3rL1f3
12

Et encore un autre pour les chaînes utilisant des promesses:

function getStream(stream) {
  return new Promise(resolve => {
    const chunks = [];

    # Buffer.from is required if chunk is a String, see comments
    stream.on("data", chunk => chunks.push(Buffer.from(chunk)));
    stream.on("end", () => resolve(Buffer.concat(chunks).toString()));
  });
}

Usage:

const stream = fs.createReadStream(__filename);
getStream(stream).then(r=>console.log(r));

supprimez le .toString()à utiliser avec des données binaires si nécessaire.

mise à jour : @AndreiLED a correctement souligné que cela avait des problèmes avec les chaînes. Je n'ai pas pu obtenir un flux renvoyant des chaînes avec la version du nœud que j'ai, mais l' API note que c'est possible.

estani
la source
J'ai remarqué que le code ci-dessus pouvait échouer Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringsi le flux produisait des stringmorceaux au lieu de Buffer. L'utilisation chunks.push(Buffer.from(chunk))devrait fonctionner avec les deux stringet les Buffermorceaux.
Andrei LED le
bon point, j'ai mis à jour la réponse. Merci.
estani le
8

À partir de la documentation de nodejs , vous devriez le faire - souvenez-vous toujours d'une chaîne sans savoir que l'encodage n'est qu'un tas d'octets:

var readable = getReadableStreamSomehow();
readable.setEncoding('utf8');
readable.on('data', function(chunk) {
  assert.equal(typeof chunk, 'string');
  console.log('got %d characters of string data', chunk.length);
})
Sébastien J.
la source
6

Les flux n'ont pas de .toString()fonction simple (que je comprends) ni quelque chose comme une .toStringAsync(cb)fonction (que je ne comprends pas).

J'ai donc créé ma propre fonction d'assistance:

var streamToString = function(stream, callback) {
  var str = '';
  stream.on('data', function(chunk) {
    str += chunk;
  });
  stream.on('end', function() {
    callback(str);
  });
}

// how to use:
streamToString(myStream, function(myStr) {
  console.log(myStr);
});
flori
la source
4

J'ai eu plus de chance en utilisant comme ça:

let string = '';
readstream
    .on('data', (buf) => string += buf.toString())
    .on('end', () => console.log(string));

J'utilise node v9.11.1et readstreamc'est la réponse d'un http.getrappel.

vdegenne
la source
3

La solution la plus propre peut être d'utiliser le package "string-stream", qui convertit un flux en chaîne avec une promesse.

const streamString = require('stream-string')

streamString(myStream).then(string_variable => {
    // myStream was converted to a string, and that string is stored in string_variable
    console.log(string_variable)

}).catch(err => {
     // myStream emitted an error event (err), so the promise from stream-string was rejected
    throw err
})
Steve Breese
la source
3

Un moyen simple avec la bibliothèque get-stream populaire (plus de 5 millions de téléchargements hebdomadaires) et légère :

https://www.npmjs.com/package/get-stream

const fs = require('fs');
const getStream = require('get-stream');

(async () => {
    const stream = fs.createReadStream('unicorn.txt');
    console.log(await getStream(stream)); //output is string
})();
Ville
la source
2

Et quelque chose comme un réducteur de flux?

Voici un exemple d'utilisation des classes ES6 comment en utiliser une.

var stream = require('stream')

class StreamReducer extends stream.Writable {
  constructor(chunkReducer, initialvalue, cb) {
    super();
    this.reducer = chunkReducer;
    this.accumulator = initialvalue;
    this.cb = cb;
  }
  _write(chunk, enc, next) {
    this.accumulator = this.reducer(this.accumulator, chunk);
    next();
  }
  end() {
    this.cb(null, this.accumulator)
  }
}

// just a test stream
class EmitterStream extends stream.Readable {
  constructor(chunks) {
    super();
    this.chunks = chunks;
  }
  _read() {
    this.chunks.forEach(function (chunk) { 
        this.push(chunk);
    }.bind(this));
    this.push(null);
  }
}

// just transform the strings into buffer as we would get from fs stream or http request stream
(new EmitterStream(
  ["hello ", "world !"]
  .map(function(str) {
     return Buffer.from(str, 'utf8');
  })
)).pipe(new StreamReducer(
  function (acc, v) {
    acc.push(v);
    return acc;
  },
  [],
  function(err, chunks) {
    console.log(Buffer.concat(chunks).toString('utf8'));
  })
);
Fred
la source
1

Cela a fonctionné pour moi et est basé sur la documentation Node v6.7.0 :

let output = '';
stream.on('readable', function() {
    let read = stream.read();
    if (read !== null) {
        // New stream data is available
        output += read.toString();
    } else {
        // Stream is now finished when read is null.
        // You can callback here e.g.:
        callback(null, output);
    }
});

stream.on('error', function(err) {
  callback(err, null);
})
anthonygore
la source
1

setEncoding ('utf8');

Bravo Sebastian J ci-dessus.

J'ai eu le "problème de tampon" avec quelques lignes de code de test que j'avais, et j'ai ajouté les informations d'encodage et cela l'a résolu, voir ci-dessous.

Démontrez le problème

Logiciel

// process.stdin.setEncoding('utf8');
process.stdin.on('data', (data) => {
    console.log(typeof(data), data);
});

contribution

hello world

production

object <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64 0d 0a>

Démontrez la solution

Logiciel

process.stdin.setEncoding('utf8'); // <- Activate!
process.stdin.on('data', (data) => {
    console.log(typeof(data), data);
});

contribution

hello world

production

string hello world
Ivan
la source
1

Toutes les réponses répertoriées semblent ouvrir le flux lisible en mode fluide, ce qui n'est pas la valeur par défaut dans NodeJS et peut avoir des limitations car il ne prend pas en charge la contre-pression que NodeJS fournit en mode de flux lisible en pause. Voici une implémentation utilisant Just Buffers, Native Stream et Native Stream Transforms et prise en charge du mode objet

import {Transform} from 'stream';

let buffer =null;    

function objectifyStream() {
    return new Transform({
        objectMode: true,
        transform: function(chunk, encoding, next) {

            if (!buffer) {
                buffer = Buffer.from([...chunk]);
            } else {
                buffer = Buffer.from([...buffer, ...chunk]);
            }
            next(null, buffer);
        }
    });
}

process.stdin.pipe(objectifyStream()).process.stdout
Herlarby
la source
1

Que pensez-vous de ceci ?

// lets a ReadableStream under stream variable 
const chunks = [];

for await (let chunk of stream) {
    chunks.push(chunk)
}

const buffer  = Buffer.concat(chunks);
const str = buffer.toString("utf-8")
Traycho Ivanov
la source
Fonctionne, très propre, pas de dépendances, sympa!
ViRuSTriNiTy
0

En utilisant le package très populairestream-buffers que vous avez probablement déjà dans les dépendances de votre projet, c'est assez simple:

// imports
const { WritableStreamBuffer } = require('stream-buffers');
const { promisify } = require('util');
const { createReadStream } = require('fs');
const pipeline = promisify(require('stream').pipeline);

// sample stream
let stream = createReadStream('/etc/hosts');

// pipeline the stream into a buffer, and print the contents when done
let buf = new WritableStreamBuffer();
pipeline(stream, buf).then(() => console.log(buf.getContents().toString()));
andrewdotn
la source
0

Dans mon cas, les en-têtes de réponse du type de contenu étaient Content-Type: text / plain . Donc, j'ai lu les données de Buffer comme:

let data = [];
stream.on('data', (chunk) => {
 console.log(Buffer.from(chunk).toString())
 data.push(Buffer.from(chunk).toString())
});
Dionis Oros
la source