Come leggo il contenuto di un flusso Node.js in una variabile stringa?


113

Sto hackerando un programma Node che utilizza smtp-protocolper acquisire e-mail SMTP e agire sui dati di posta. La libreria fornisce i dati di posta come flusso e non so come inserirli in una stringa.

Attualmente lo sto scrivendo su stdout con stream.pipe(process.stdout, { end: false }), ma come ho detto, ho bisogno invece dei dati del flusso in una stringa, che posso usare una volta terminato il flusso.

Come raccolgo tutti i dati da un flusso Node.js in una stringa?


Dovresti copiare lo stream o contrassegnarlo con (autoClose: false). È una cattiva pratica inquinare la memoria.
19h

Risposte:


41

(Questa risposta è di anni fa, quando era la risposta migliore. Ora c'è una risposta migliore sotto questa. Non ho tenuto il passo con node.js e non posso eliminare questa risposta perché è contrassegnata come "corretta su questa domanda ". Se stai pensando di fare clic in basso, cosa vuoi che faccia?)

La chiave è usare gli eventi datae enddi un flusso leggibile . Ascolta questi eventi:

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

Quando ricevi l' dataevento, aggiungi il nuovo blocco di dati a un buffer creato per raccogliere i dati.

Quando ricevi l' endevento, converti il ​​Buffer completato in una stringa, se necessario. Quindi fai quello che devi fare con esso.


149
Un paio di righe di codice che illustrano la risposta sono preferibili al semplice puntamento di un collegamento all'API. Non essere in disaccordo con la risposta, semplicemente non credere che sia abbastanza completa.
arcseldon

3
Con le versioni più recenti di node.js, questo è più pulito: stackoverflow.com/a/35530615/271961
Simon A. Eugster

La risposta dovrebbe essere aggiornata per non consigliare l'utilizzo di una libreria Promises, ma utilizzare Promises nativo.
Dan Dascalescu

@DanDascalescu sono d'accordo con te. Il problema è che ho scritto questa risposta 7 anni fa e non ho tenuto il passo con node.js. Se sei qualcun altro e desideri aggiornarlo, sarebbe fantastico. Oppure potrei semplicemente cancellarlo, poiché sembra che ci sia già una risposta migliore. Cosa raccomanderesti?
ControlAltDel

@ControlAltDel: apprezzo la tua iniziativa di eliminare una risposta che non è più la migliore. Vorrei che gli altri avessero una disciplina simile .
Dan Dascalescu

129

Un altro modo sarebbe convertire il flusso in una promessa (fare riferimento all'esempio seguente) e utilizzare then(o await) per assegnare il valore risolto a una variabile.

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)

Sono veramente nuovo da corsi d'acqua e le promesse e sto ottenendo questo errore: SyntaxError: await is only valid in async function. Che cosa sto facendo di sbagliato?
JohnK

Devi chiamare la funzione streamtostring all'interno di una funzione asincrona. Per evitare ciò puoi anche farestreamToString(stream).then(function(response){//Do whatever you want with response});
Enclo Creations

23
Questa dovrebbe essere la risposta migliore. Congratulazioni per aver prodotto l'unica soluzione che fa tutto bene, con (1) memorizzare i blocchi come Buffer e chiamare solo .toString("utf8")alla fine, per evitare il problema di un errore di decodifica se un blocco è diviso nel mezzo di un carattere multibyte; (2) gestione degli errori effettivi; (3) mettere il codice in una funzione, in modo che possa essere riutilizzato, non copiato; (4) utilizzando Promesse in modo che la funzione possa essere awaitattivata; (5) codice piccolo che non trascina un milione di dipendenze, a differenza di alcune librerie npm; (6) Sintassi ES6 e best practice moderne.
MultiplyByZer0

Perché non spostare l'array di blocchi nella promessa?
Jenny O'Reilly,

1
Dopo aver elaborato essenzialmente lo stesso codice utilizzando la risposta principale corrente come suggerimento, ho notato che il codice sopra potrebbe fallire Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringse il flusso produce stringblocchi invece di Buffer. Utilizzando chunks.push(Buffer.from(chunk))dovrebbe funzionare con entrambi stringe Bufferpezzi.
Andrei LED

67

Nessuno dei precedenti ha funzionato per me. Avevo bisogno di usare l'oggetto 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
questo è in realtà il modo più pulito per farlo;)
Ivo

7
Funziona alla grande. Solo una nota: se vuoi un tipo di stringa corretto, dovrai chiamare .toString () sull'oggetto Buffer risultante dalla chiamata concat ()
Bryan Johnson,

64

Spero che questo sia più utile della risposta sopra:

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

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

Nota che la concatenazione di stringhe non è il modo più efficiente per raccogliere le parti di stringa, ma è usata per semplicità (e forse il tuo codice non si preoccupa dell'efficienza).

Inoltre, questo codice può produrre errori imprevedibili per il testo non ASCII (presuppone che ogni carattere si adatti a un byte), ma forse non ti interessa neanche questo.


4
Quale sarebbe un modo più efficiente per raccogliere parti di archi? TY
sean2078

2
potresti usare un buffer docs.nodejitsu.com/articles/advanced/buffers/how-to-use-buffers ma dipende davvero dal tuo utilizzo.
Tom Carchrae,

2
Usa un array di stringhe in cui aggiungi ogni nuovo blocco all'array e chiama join("")l'array alla fine.
Valeriu Paloş

14
Questo non è giusto. Se il buffer è a metà di un punto di codice multibyte, toString () riceverà utf-8 non valido e ti ritroverai con un gruppo di nella stringa.
alextgordon

2
@alextgordon ha ragione. In alcuni casi molto rari, quando avevo molti pezzi, li ottenevo all'inizio e alla fine dei pezzi. Soprattutto quando c'erano simboli russi sui bordi. Quindi è corretto concatenare i blocchi e convertirli alla fine invece di convertirli e concatenarli. Nel mio caso la richiesta è stata effettuata da un servizio all'altro con request.js con codifica predefinita
Mike Yermolayev

21

Di solito sto usando questa semplice funzione per trasformare un flusso in una stringa:

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

Esempio di utilizzo:

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

1
Risposta utile ma sembra che ogni blocco debba essere convertito in una stringa prima di essere inserito nell'array:chunks.push(chunk.toString());
Nicolas Le Thierry d'Ennequin,

1
Questo è l'unico che ha funzionato per me! Grazie
mille

1
Questa è stata un'ottima risposta!
Aft3rL1f3

12

E ancora un altro per le stringhe che usano le promesse:

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

rimuovere .toString()da utilizzare con i dati binari, se necessario.

aggiornamento : @AndreiLED ha correttamente sottolineato che ha problemi con le stringhe. Non sono riuscito a ottenere un flusso che restituisce stringhe con la versione del nodo che ho, ma l' API osserva che questo è possibile.


Ho notato che il codice sopra potrebbe non riuscire con Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringse il flusso produce stringblocchi invece di Buffer. Utilizzando chunks.push(Buffer.from(chunk))dovrebbe funzionare con entrambi stringe Bufferpezzi.
Andrei LED

buon punto, ho aggiornato la risposta. Grazie.
estani

8

Dalla documentazione di nodejs dovresti farlo: ricorda sempre una stringa senza sapere che la codifica è solo un mucchio di byte:

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

I flussi non hanno una .toString()funzione semplice (che capisco) né qualcosa come una .toStringAsync(cb)funzione (che non capisco).

Quindi ho creato la mia funzione di supporto:

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

Ho avuto più fortuna usando in questo modo:

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

Uso il nodo v9.11.1e questa readstreamè la risposta di una http.getrichiamata.


3

La soluzione più pulita potrebbe essere quella di utilizzare il pacchetto "string-stream", che converte un flusso in una stringa con una promessa.

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

Modo semplice con la popolare libreria get-stream (oltre 5 milioni di download settimanali) e leggera :

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

Che ne dici di qualcosa come un riduttore di flusso?

Ecco un esempio che utilizza le classi ES6 su come utilizzarne una.

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

Questo ha funzionato per me ed è basato sui documenti di 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');

Ben fatto Sebastian J sopra.

Ho avuto il "problema del buffer" con poche righe di codice di prova, ho aggiunto le informazioni di codifica e l'ho risolto, vedi sotto.

Dimostra il problema

Software

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

ingresso

hello world

produzione

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

Dimostrare la soluzione

Software

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

ingresso

hello world

produzione

string hello world

1

Tutte le risposte elencate sembrano aprire il flusso leggibile in modalità di flusso che non è l'impostazione predefinita in NodeJS e può avere limitazioni poiché manca il supporto di contropressione che NodeJS fornisce in modalità di flusso leggibile in pausa. Ecco un'implementazione che utilizza Just Buffers, Native Stream e Native Stream Transforms e il supporto per la modalità oggetto

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

Cosa ne pensi di questo ?

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

Funziona, molto pulito, senza dipendenze, bello!
ViRuSTriNiTy

0

Usando il pacchetto abbastanza popolarestream-buffers che probabilmente hai già nelle dipendenze del tuo progetto, questo è abbastanza semplice:

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

Nel mio caso, le intestazioni di risposta del tipo di contenuto erano Content-Type: text / plain . Quindi, ho letto i dati da Buffer come:

let data = [];
stream.on('data', (chunk) => {
 console.log(Buffer.from(chunk).toString())
 data.push(Buffer.from(chunk).toString())
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.