Bagaimana cara membaca konten aliran Node.js menjadi variabel string?

113

Saya meretas program Node yang digunakan smtp-protocoluntuk menangkap email SMTP dan bertindak atas data email. Perpustakaan menyediakan data email sebagai aliran, dan saya tidak tahu cara memasukkannya ke dalam string.

Saat ini saya menulisnya ke stdout stream.pipe(process.stdout, { end: false }), tetapi seperti yang saya katakan, saya memerlukan data aliran dalam string, yang dapat saya gunakan setelah aliran berakhir.

Bagaimana cara mengumpulkan semua data dari aliran Node.js menjadi string?

obrienmd
sumber
Anda harus menyalin aliran atau menandainya dengan (autoClose: false). Ini adalah praktik yang buruk untuk mengotori ingatan.
19 jam

Jawaban:

41

(Jawaban ini dari beberapa tahun yang lalu, ketika itu adalah jawaban terbaik. Sekarang ada jawaban yang lebih baik di bawah ini. Saya belum mengikuti node.js, dan saya tidak dapat menghapus jawaban ini karena sudah ditandai "benar untuk pertanyaan ini ". Jika Anda berpikir untuk mengklik ke bawah, apa yang Anda ingin saya lakukan?)

Kuncinya adalah menggunakan peristiwa datadan enddari Arus yang Dapat Dibaca . Dengarkan acara ini:

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

Saat Anda menerima dataacara, tambahkan potongan data baru ke Buffer yang dibuat untuk mengumpulkan data.

Saat Anda menerima endacara, ubah Buffer yang telah selesai menjadi string, jika perlu. Kemudian lakukan apa yang perlu Anda lakukan dengannya.

ControlAltDel
sumber
149
Beberapa baris kode yang menggambarkan jawabannya lebih disukai daripada hanya mengarahkan tautan ke API. Jangan tidak setuju dengan jawabannya, hanya saja jangan percaya itu sudah cukup lengkap.
arcseldon
3
Dengan versi node.js yang lebih baru, ini lebih bersih: stackoverflow.com/a/35530615/271961
Simon A.Eugster
Jawabannya harus diupdate agar tidak merekomendasikan penggunaan library Promises, tetapi gunakan Promises asli.
Dan Dascalescu
@DanDascalescu Saya setuju dengan Anda. Masalahnya adalah saya menulis jawaban ini 7 tahun yang lalu, dan saya belum mengikuti node.js. Jika Anda adalah orang lain yang ingin memperbaruinya, itu akan bagus. Atau saya bisa langsung menghapusnya, karena sepertinya sudah ada jawaban yang lebih baik. Apa yang akan Anda rekomendasikan?
ControlAltDel
@ControlAltDel: Saya menghargai inisiatif Anda untuk menghapus jawaban yang bukan lagi yang terbaik. Berharap orang lain memiliki disiplin yang sama .
Dan Dascalescu
129

Cara lain adalah dengan mengonversi aliran menjadi promise (lihat contoh di bawah) dan menggunakan then(atau await) untuk menetapkan nilai yang diselesaikan ke variabel.

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
sumber
Aku benar-benar baru untuk sungai dan janji-janji dan aku mendapatkan kesalahan ini: SyntaxError: await is only valid in async function. Apa yang saya lakukan salah?
JohnK
Anda harus memanggil fungsi streamtostring dalam fungsi async. Untuk menghindari hal ini, Anda juga dapat melakukanstreamToString(stream).then(function(response){//Do whatever you want with response});
Enclo Creations
23
Ini harus menjadi jawaban teratas. Selamat atas menghasilkan satu-satunya solusi yang membuat segalanya benar, dengan (1) menyimpan potongan sebagai Buffer dan hanya memanggil .toString("utf8")di akhir, untuk menghindari masalah kegagalan decode jika sebuah potongan terbelah di tengah karakter multibyte; (2) penanganan kesalahan yang sebenarnya; (3) menempatkan kode dalam suatu fungsi, sehingga dapat digunakan kembali, bukan disalin-tempel; (4) menggunakan Promises sehingga fungsinya dapat awaitdiaktifkan; (5) kode kecil yang tidak menyeret jutaan dependensi, tidak seperti library npm tertentu; (6) sintaks ES6 dan praktik terbaik modern.
MultiplyByZer0
Mengapa tidak memindahkan larik potongan ke dalam promise?
Jenny O'Reilly
1
Setelah saya menemukan kode yang pada dasarnya sama menggunakan jawaban teratas saat ini sebagai petunjuk, saya telah memperhatikan bahwa kode di atas bisa gagal Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringjika aliran menghasilkan stringpotongan, bukan Buffer. Menggunakan chunks.push(Buffer.from(chunk))harus bekerja dengan baik stringdan Bufferpotongan.
Andrei LED
67

Semua hal di atas tidak berhasil untuk saya. Saya perlu menggunakan objek 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
sumber
7
ini sebenarnya cara terbersih untuk melakukannya;)
Ivo
7
Bekerja dengan baik. Sekadar catatan: jika Anda menginginkan tipe string yang tepat, Anda perlu memanggil .toString () pada objek Buffer yang dihasilkan dari panggilan concat ()
Bryan Johnson
64

Semoga ini lebih bermanfaat dari jawaban di atas:

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

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

Perhatikan bahwa penggabungan string bukanlah cara yang paling efisien untuk mengumpulkan bagian string, tetapi digunakan untuk kesederhanaan (dan mungkin kode Anda tidak mempedulikan efisiensi).

Selain itu, kode ini dapat menghasilkan kegagalan tak terduga untuk teks non-ASCII (mengasumsikan bahwa setiap karakter cocok dalam satu byte), tetapi mungkin Anda juga tidak peduli tentang itu.

Tom Carchrae
sumber
4
Apa cara yang lebih efisien untuk mengumpulkan bagian string? TY
sean2078
2
Anda dapat menggunakan buffer docs.nodejitsu.com/articles/advanced/buffers/how-to-use-buffers tetapi itu benar-benar tergantung pada penggunaan Anda.
Tom Carchrae
2
Gunakan array string tempat Anda menambahkan setiap potongan baru ke array dan memanggil join("")array di akhir.
Valeriu Paloş
14
Ini tidak benar. Jika buffer setengah jalan melalui titik kode multi-byte maka toString () akan menerima utf-8 dalam format yang salah dan Anda akan berakhir dengan sekelompok dalam string Anda.
alextgordon
2
@alextgordon benar. Dalam beberapa kasus yang sangat jarang terjadi ketika saya memiliki banyak potongan, saya mendapatkannya di awal dan akhir potongan. Apalagi bila ada di mana simbol Rusia di tepinya. Jadi adalah benar untuk menggabungkan potongan dan mengubahnya pada akhirnya daripada mengubah potongan dan menggabungkannya. Dalam kasus saya, permintaan dibuat dari satu layanan ke layanan lainnya dengan request.js dengan pengkodean default
Mike Yermolayev
21

Saya biasanya menggunakan fungsi sederhana ini untuk mengubah aliran menjadi string:

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

Contoh penggunaan:

let stream = fs.createReadStream('./myFile.foo');
streamToString(stream, (data) => {
  console.log(data);  // data is now my string variable
});
dreampulse
sumber
1
Jawaban yang berguna tetapi sepertinya setiap potongan harus dikonversi menjadi string sebelum didorong ke dalam array:chunks.push(chunk.toString());
Nicolas Le Thierry d'Ennequin
1
Ini adalah satu-satunya yang berhasil untuk saya! Terima kasih banyak
538ROMEO
1
Ini jawaban yang bagus!
Aft3rL1f3
12

Dan satu lagi untuk string menggunakan promise:

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

Pemakaian:

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

hapus .toString()untuk digunakan dengan Data biner jika diperlukan.

update : @AndreiLED dengan benar menunjukkan bahwa ini memiliki masalah dengan string. Saya tidak bisa mendapatkan aliran yang mengembalikan string dengan versi node yang saya miliki, tetapi api mencatat hal ini dimungkinkan.

estani
sumber
Saya telah memperhatikan bahwa kode di atas bisa gagal Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringjika aliran menghasilkan stringpotongan, bukan Buffer. Menggunakan chunks.push(Buffer.from(chunk))harus bekerja dengan baik stringdan Bufferpotongan.
Andrei LED
poin yang bagus, saya telah memperbarui jawabannya. Terima kasih.
estani
8

Dari dokumentasi nodejs Anda harus melakukan ini - selalu ingat string tanpa mengetahui encoding hanya sekumpulan 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);
})
Sebastian J.
sumber
6

Stream tidak memiliki .toString()fungsi sederhana (yang saya mengerti) atau sesuatu seperti .toStringAsync(cb)fungsi (yang saya tidak mengerti).

Jadi saya membuat fungsi pembantu saya sendiri:

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
sumber
4

Saya lebih beruntung menggunakan seperti itu:

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

Saya menggunakan node v9.11.1dan readstreammerupakan respon dari http.getpanggilan balik.

vdegenne.dll
sumber
3

Solusi terbersih mungkin menggunakan paket "string-stream", yang mengonversi aliran menjadi string dengan janji.

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
sumber
3

Cara mudah dengan perpustakaan populer (lebih dari 5 juta unduhan mingguan) dan pustaka get-stream ringan :

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
sumber
2

Bagaimana dengan sesuatu seperti peredam aliran?

Berikut adalah contoh penggunaan kelas ES6 cara menggunakannya.

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
sumber
1

Ini berfungsi untuk saya dan didasarkan pada dokumen 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
sumber
1

setEncoding ('utf8');

Kerja bagus Sebastian J di atas.

Saya memiliki "masalah buffer" dengan beberapa baris kode pengujian yang saya miliki, dan menambahkan informasi pengkodean dan menyelesaikannya, lihat di bawah.

Tunjukkan masalahnya

perangkat lunak

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

memasukkan

hello world

keluaran

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

Peragakan solusinya

perangkat lunak

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

memasukkan

hello world

keluaran

string hello world
Ivan
sumber
1

Semua jawaban yang tercantum tampaknya membuka Stream yang Dapat Dibaca dalam mode mengalir yang bukan default di NodeJS dan dapat memiliki batasan karena tidak memiliki dukungan tekanan balik yang disediakan NodeJS dalam Mode Streaming Dapat Dibaca yang Dijeda. Berikut adalah implementasi menggunakan Just Buffer, Native Stream dan Native Stream Transforms dan dukungan untuk Object Mode

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
sumber
1

Apa pendapat Anda tentang ini?

// 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
sumber
Bekerja, sangat bersih, tidak ada ketergantungan, bagus!
ViRuSTriNiTy
0

Menggunakan paket yang cukup populerstream-buffers yang mungkin sudah Anda miliki di dependensi proyek Anda, ini cukup mudah:

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

Dalam kasus saya, header respons tipe konten adalah Tipe-Konten: teks / biasa . Jadi, saya sudah membaca data dari Buffer seperti:

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