¿Cómo leo el contenido de un flujo de Node.js en una variable de cadena?


113

Estoy pirateando un programa de nodo que se utiliza smtp-protocolpara capturar correos electrónicos SMTP y actuar sobre los datos del correo. La biblioteca proporciona los datos del correo como una secuencia, y no sé cómo convertirlos en una cadena.

Actualmente lo estoy escribiendo en stdout con stream.pipe(process.stdout, { end: false }), pero como dije, necesito los datos de la transmisión en una cadena, que puedo usar una vez que la transmisión haya finalizado.

¿Cómo recopilo todos los datos de un flujo de Node.js en una cadena?


Debe copiar la transmisión o marcarla con (autoClose: false). Es una mala práctica contaminar la memoria.
19h

Respuestas:


41

(Esta respuesta es de hace años, cuando era la mejor respuesta. Ahora hay una mejor respuesta debajo de esta. No me he mantenido al día con node.js y no puedo eliminar esta respuesta porque está marcada como "correcta en esta pregunta ". Si está pensando en hacer clic hacia abajo, ¿qué quiere que haga?)

La clave es utilizar los eventos datay endde una secuencia legible . Escuche estos eventos:

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

Cuando reciba el dataevento, agregue la nueva porción de datos a un búfer creado para recopilar los datos.

Cuando reciba el endevento, convierta el búfer completo en una cadena, si es necesario. Luego haz lo que tengas que hacer con él.


149
Es preferible un par de líneas de código que ilustren la respuesta a simplemente apuntar un enlace a la API. No esté en desacuerdo con la respuesta, simplemente no crea que sea lo suficientemente completa.
arcseldon

3
Con las versiones más nuevas de node.js, esto es más limpio: stackoverflow.com/a/35530615/271961
Simon A. Eugster

La respuesta debe actualizarse para no recomendar el uso de una biblioteca de promesas, sino usar promesas nativas.
Dan Dascalescu

@DanDascalescu Estoy de acuerdo contigo. El problema es que escribí esta respuesta hace 7 años y no me he mantenido al día con node.js. Si usted es alguien más y le gustaría actualizarlo, sería genial. O simplemente podría eliminarlo, ya que parece haber una mejor respuesta. ¿Qué recomendarías?
ControlAltDel

@ControlAltDel: Agradezco su iniciativa de eliminar una respuesta que ya no es la mejor. Ojalá otros tuvieran una disciplina similar .
Dan Dascalescu

129

Otra forma sería convertir la secuencia en una promesa (consulte el ejemplo a continuación) y usar then(o await) para asignar el valor resuelto a una 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)

Estoy muy nuevo en arroyos y promesas y yo estoy recibiendo este error: SyntaxError: await is only valid in async function. ¿Qué estoy haciendo mal?
JohnK

Tienes que llamar a la función streamtostring dentro de una función asincrónica. Para evitar esto también puede hacerstreamToString(stream).then(function(response){//Do whatever you want with response});
Enclo Creations

23
Esta debería ser la mejor respuesta. Felicitaciones por producir la única solución que hace todo bien, con (1) almacenar los fragmentos como Buffers y solo llamar .toString("utf8")al final, para evitar el problema de una falla en la decodificación si un fragmento se divide en medio de un carácter multibyte; (2) manejo real de errores; (3) poner el código en una función, para que se pueda reutilizar, no copiar y pegar; (4) usar Promesas para que se pueda activar la función await; (5) código pequeño que no arrastra un millón de dependencias, a diferencia de ciertas bibliotecas npm; (6) Sintaxis ES6 y mejores prácticas modernas.
MultiplyByZer0

¿Por qué no mover la matriz de fragmentos a la promesa?
Jenny O'Reilly

1
Después de que se me ocurrió esencialmente el mismo código usando la respuesta principal actual como sugerencia, he notado que el código anterior podría fallar Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringsi la transmisión produce stringfragmentos en lugar de Buffer. El uso chunks.push(Buffer.from(chunk))debería funcionar con ambos stringy Buffertrozos.
Andrei LED

67

Ninguno de los anteriores funcionó para mí. Necesitaba usar el objeto 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));
  });

7
esta es en realidad la forma más limpia de hacerlo;)
Ivo

7
Funciona genial. Solo una nota: si desea un tipo de cadena adecuado, deberá llamar .toString () en el objeto Buffer resultante de la llamada concat ()
Bryan Johnson

64

Espero que esto sea más útil que la respuesta anterior:

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

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

Tenga en cuenta que la concatenación de cadenas no es la forma más eficiente de recopilar las partes de la cadena, pero se usa para simplificar (y quizás a su código no le importa la eficiencia).

Además, este código puede producir fallas impredecibles para texto que no es ASCII (asume que cada carácter cabe en un byte), pero tal vez eso tampoco le importe.


4
¿Cuál sería una forma más eficiente de recolectar partes de cuerdas? TY
sean2078

2
podría usar un búfer docs.nodejitsu.com/articles/advanced/buffers/how-to-use-buffers pero realmente depende de su uso.
Tom Carchrae

2
Use una matriz de cadenas en la que agregue cada nuevo fragmento a la matriz y llame join("")a la matriz al final.
Valeriu Paloş

14
Esto no es correcto. Si el búfer está a la mitad de un punto de código multibyte, toString () recibirá utf-8 con formato incorrecto y terminará con un montón de en su cadena.
alextgordon

2
@alextgordon tiene razón. En algunos casos muy raros, cuando tenía muchos trozos, los obtenía al principio y al final de los trozos. Especialmente cuando hay símbolos rusos en los bordes. Por lo tanto, es correcto concatizar fragmentos y convertirlos en un extremo en lugar de convertir fragmentos y concatenarlos. En mi caso, la solicitud se realizó de un servicio a otro con request.js con codificación predeterminada
Mike Yermolayev

21

Normalmente uso esta función simple para transformar una secuencia en una cadena:

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

Ejemplo de uso:

let stream = fs.createReadStream('./myFile.foo');
streamToString(stream, (data) => {
  console.log(data);  // data is now my string variable
});

1
Respuesta útil, pero parece que cada fragmento debe convertirse en una cadena antes de que se inserte en la matriz:chunks.push(chunk.toString());
Nicolas Le Thierry d'Ennequin

1
¡Este es el único que funcionó para mí!
Muchas

1
¡Esta fue una gran respuesta!
Aft3rL1f3

12

Y otro más para cadenas que usan promesas:

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()));
  });
}

Uso:

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

elimine el .toString()para usar con datos binarios si es necesario.

actualización : @AndreiLED señaló correctamente que esto tiene problemas con las cadenas. No pude obtener una secuencia que devuelva cadenas con la versión del nodo que tengo, pero la API señala que esto es posible.


He notado que el código anterior podría fallar Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringsi la transmisión produce stringfragmentos en lugar de Buffer. El uso chunks.push(Buffer.from(chunk))debería funcionar con ambos stringy Buffertrozos.
Andrei LED

buen punto, he actualizado la respuesta. Gracias.
estani

8

De la documentación de nodejs , debe hacer esto: recuerde siempre una cadena sin saber que la codificación es solo un montón de bytes:

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);
})

6

Los flujos no tienen una .toString()función simple (que entiendo) ni algo así como una .toStringAsync(cb)función (que no entiendo).

Entonces creé mi propia función de ayuda:

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);
});

4

Tuve más suerte usando así:

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

Yo uso el nodo v9.11.1y readstreames la respuesta de una http.getdevolución de llamada.


3

La solución más limpia puede ser utilizar el paquete "string-stream", que convierte una secuencia en una cadena con una promesa.

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
})

3

Manera fácil con la popular (más de 5 millones de descargas semanales) y la biblioteca ligera get-stream :

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
})();

2

¿Qué tal algo como un reductor de corriente?

A continuación, se muestra un ejemplo de cómo utilizar las clases de ES6.

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'));
  })
);

1

Esto funcionó para mí y se basa en los documentos de 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);
})

1

setEncoding ('utf8');

Bien hecho Sebastian J arriba.

Tuve el "problema del búfer" con algunas líneas de código de prueba que tenía, agregué la información de codificación y lo resolvió, ver más abajo.

Demuestra el problema

software

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

entrada

hello world

salida

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

Demuestre la solución

software

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

entrada

hello world

salida

string hello world

1

Todas las respuestas enumeradas parecen abrir la transmisión legible en modo de flujo, que no es el predeterminado en NodeJS y puede tener limitaciones, ya que carece de soporte de contrapresión que proporciona NodeJS en el modo de transmisión legible en pausa. Aquí hay una implementación que utiliza Just Buffers, Native Stream y Native Stream Transforms y compatibilidad con el modo de objeto.

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

1

Qué piensas sobre esto ?

// 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")

Funciona, muy limpio, sin dependencias, ¡bonito!
ViRuSTriNiTy hace

0

Usando el paquete bastante popularstream-buffers que probablemente ya tenga en las dependencias de su proyecto, esto es bastante sencillo:

// 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()));

0

En mi caso, los encabezados de respuesta del tipo de contenido eran Content-Type: text / plain . Entonces, he leído los datos de Buffer como:

let data = [];
stream.on('data', (chunk) => {
 console.log(Buffer.from(chunk).toString())
 data.push(Buffer.from(chunk).toString())
});
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.