Bir Node.js akışının içeriğini bir dize değişkenine nasıl okuyabilirim?


113

smtp-protocolSMTP e-postalarını yakalamak ve posta verileri üzerinde işlem yapmak için kullanan bir Düğüm programını hackliyorum . Kitaplık, posta verilerini bir akış olarak sağlar ve bunu bir dizeye nasıl sokacağımı bilmiyorum.

Şu anda stdout ile yazıyorum stream.pipe(process.stdout, { end: false }), ancak dediğim gibi, bunun yerine akış verisine bir dizede ihtiyacım var, bunu akış sona erdiğinde kullanabilirim.

Bir Node.js akışındaki tüm verileri bir dizeye nasıl toplarım?


Akışı kopyalamanız veya (autoClose: false) ile işaretlemeniz gerekir. Hafızayı kirletmek kötü bir uygulamadır.
19h

Yanıtlar:


41

(Bu yanıt, yıllar öncesinden, en iyi yanıt olduğu zamandan. Şimdi bunun altında daha iyi bir yanıt var. Node.js ile devam etmedim ve bu soruda "doğru" olarak işaretlendiği için bu yanıtı silemiyorum ". Aşağı tıklamayı düşünüyorsanız, ne yapmamı istersiniz?)

Anahtar, Okunabilir Akışındata ve endolaylarını kullanmaktır . Şu olayları dinleyin:

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

dataOlayı aldığınızda, yeni veri yığınını verileri toplamak için oluşturulan bir Arabelleğe ekleyin.

endOlayı aldığınızda , gerekirse tamamlanan Buffer'ı bir dizeye dönüştürün. O zaman onunla ne yapman gerekiyorsa yap.


149
Yanıtı gösteren birkaç satırlık kod, yalnızca API'ye bir bağlantı işaret etmektense tercih edilir. Cevaba katılmayın, sadece yeterince eksiksiz olduğuna inanmayın.
arcseldon

3
Daha yeni node.js sürümleriyle bu daha temiz: stackoverflow.com/a/35530615/271961
Simon A. Eugster

Cevap, bir Promises kitaplığı kullanmanızı tavsiye etmemek, ancak yerel Promises'i kullanmak için güncellenmelidir.
Dan Dascalescu

@DanDascalescu Size katılıyorum. Sorun şu ki, bu cevabı 7 yıl önce yazdım ve node.js ile yetişemedim. Eğer başka biriyseniz onu güncellemek isterse, bu harika olur. Veya daha iyi bir cevap var gibi göründüğü için onu basitçe silebilirim. Ne önerirsiniz?
ControlAltDel

@ControlAltDel: Artık en iyisi olmayan bir cevabı silme girişiminiz için teşekkür ederim. Keşke başkalarının da benzer disipline sahip olması .
Dan Dascalescu

129

Başka bir yol, akışı bir söze dönüştürmek (aşağıdaki örneğe bakın) ve çözümlenen değeri bir değişkene atamak için then(veya await) kullanmaktır .

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)

Ben akışları ve vaatlerine gerçekten yeni ve ben bu hatayı alıyorum: SyntaxError: await is only valid in async function. Neyi yanlış yapıyorum?
JohnK

Bir zaman uyumsuz işlev içinde streamtostring işlevini çağırmanız gerekir. Bundan kaçınmak için şunları da yapabilirsinizstreamToString(stream).then(function(response){//Do whatever you want with response});
Enclo Creations

23
Bu en iyi cevap olmalı. Her şeyi doğru yapan tek çözümü ürettiğiniz için tebrikler, (1) yığınları Tamponlar olarak depolayarak ve yalnızca .toString("utf8")sonunda çağırarak , bir yığın çok baytlı bir karakterin ortasında bölünürse kod çözme hatası sorununu önlemek için; (2) gerçek hata işleme; (3) kodu kopyalayıp yapıştırmak yerine yeniden kullanılabilmesi için bir işleve koymak; (4) Fonksiyonun açık olması için Promises'in kullanılması await; (5) belirli npm kitaplıklarının aksine bir milyon bağımlılıkta sürüklenmeyen küçük kod; (6) ES6 sözdizimi ve modern en iyi uygulamalar.
MultiplyByZer0

Neden chunks dizisini sözün içine taşımıyorsunuz?
Jenny O'Reilly

1
Şu anki en iyi yanıtı ipucu olarak kullanarak esasen aynı kodu bulduktan sonra Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type string, akış stringyerine yığınlar üretirse yukarıdaki kodun başarısız olabileceğini fark ettim Buffer. Kullanımı chunks.push(Buffer.from(chunk))hem çalışmalıdır stringve Bufferparçalar.
Andrei LED

67

Yukarıdakilerin hiçbiri benim için işe yaramadı. Buffer nesnesini kullanmam gerekiyordu:

  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
bu aslında bunu yapmanın en temiz yolu;)
Ivo

7
Harika çalışıyor. Sadece bir not: Eğer uygun bir dize türü istiyorsanız, sonuçta elde edilen Buffer nesnesinde .toString () 'yi concat () çağrısından
Bryan Johnson

64

Umarım bu yukarıdaki cevaptan daha yararlıdır:

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

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

Dize birleştirme işleminin, dizi parçalarını toplamanın en verimli yolu olmadığını, ancak basitlik için kullanıldığını (ve belki de kodunuzun verimliliği önemsemediğini) unutmayın.

Ayrıca, bu kod ASCII olmayan metinler için öngörülemeyen hatalar üretebilir (her karakterin bir bayta sığdığını varsayar), ancak belki de bunu umursamıyorsunuzdur.


4
İp parçalarını toplamanın daha verimli bir yolu ne olabilir? TY
sean2078

2
bir arabellek docs.nodejitsu.com/articles/advanced/buffers/how-to-use-buffers kullanabilirsiniz, ancak bu gerçekten kullanımınıza bağlıdır.
Tom Carchrae

2
Her yeni öbeği diziye eklediğiniz join("")ve sondaki diziyi çağırdığınız bir dizi kullanın .
Valeriu Paloş

14
Bu doğru değil. Arabellek çok baytlı bir kod noktasının yarısındaysa, toString () hatalı biçimlendirilmiş utf-8 alır ve dizenizde bir grup ile sonuçlanır.
alextgordon

2
@alextgordon haklı. Çok sayıda parçam olduğu çok nadir durumlarda, bunları parçaların başında ve sonunda aldım. Özellikle kenarlarda rusça sembollerin olduğu yerde. Bu nedenle, parçaları dönüştürmek ve birleştirmek yerine parçaları birleştirmek ve bunları uçta dönüştürmek doğrudur. Benim durumumda istek, varsayılan kodlamalı request.js ile bir hizmetten diğerine yapıldı
Mike Yermolayev

21

Bir akışı dizeye dönüştürmek için genellikle bu basit işlevi kullanıyorum:

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

Kullanım örneği:

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

1
Faydalı cevap, ancak görünüşe göre her bir parçanın diziye gönderilmeden önce bir dizeye dönüştürülmesi gerekiyor: chunks.push(chunk.toString());
Nicolas Le Thierry d'Ennequin

1
Bu benim için çalışan tek kişi! Çok teşekkürler
538ROMEO

1
Bu harika bir cevaptı!
Aft3rL1f3

12

Ve vaatleri kullanan dizeler için bir tane daha:

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

Kullanım:

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

.toString()gerekirse ikili verilerle kullanmak için kaldırın .

Güncelleme : @AndreiLED doğru bir şekilde bunun dizelerle ilgili sorunları olduğunu belirtti. Sahip olduğum düğüm sürümüyle dizeleri döndüren bir akış elde edemedim, ancak api bunun mümkün olduğunu belirtiyor.


Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringAkış stringyerine parçalar üretirse yukarıdaki kodun başarısız olabileceğini fark ettim Buffer. Kullanımı chunks.push(Buffer.from(chunk))hem çalışmalıdır stringve Bufferparçalar.
Andrei LED

iyi nokta, cevabı güncelledim. Teşekkürler.
estani

8

Nodejs belgelerine göre bunu yapmalısınız - kodlamanın sadece bir grup bayt olduğunu bilmeden her zaman bir dizeyi hatırlayın:

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

Akışların basit bir .toString()işlevi (anladığım) veya işlev gibi (anlamadığım) bir şey .toStringAsync(cb)yok.

Bu yüzden kendi yardımcı işlevimi yarattım:

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

Böyle kullanırken daha çok şansım oldu:

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

Düğümü kullanıyorum v9.11.1ve geri aramadan readstreamgelen yanıt http.get.


3

En temiz çözüm, bir akışı bir sözle dizgeye dönüştüren "dizgi akışı" paketini kullanmak olabilir.

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

Popüler (haftada 5 milyondan fazla indirme) ve hafif get-stream kitaplığı ile kolay yol :

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

Akış azaltıcı gibi bir şeye ne dersiniz?

İşte ES6 sınıflarının nasıl kullanılacağıyla ilgili bir örnek.

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

Bu benim için çalıştı ve Node v6.7.0 belgelerine dayanıyor :

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

Aferin Sebastian J yukarıda.

Sahip olduğum birkaç satırlık test koduyla "tampon problemi" yaşadım ve kodlama bilgisini ekledim ve çözdü, aşağıya bakın.

Sorunu gösterin

yazılım

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

giriş

hello world

çıktı

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

Çözümü gösterin

yazılım

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

giriş

hello world

çıktı

string hello world

1

Listelenen tüm yanıtlar Okunabilir Akışı, NodeJS'de varsayılan olmayan akış modunda açıyor gibi görünmektedir ve NodeJS'nin Duraklatılmış Okunabilir Akış Modunda sağladığı geri basınç desteğinden yoksun olduğundan sınırlamaları olabilir. İşte Just Buffers, Native Stream ve Native Stream Transforms kullanan bir uygulama ve Nesne Modu desteği

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

Bunun hakkında ne düşünüyorsun ?

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

Çalışıyor, çok temiz, bağımlılık yok, güzel!
ViRuSTriNiTy

0

Muhtemelen proje bağımlılıklarınızda zaten sahip olduğunuz oldukça popüler stream-bufferspaketi kullanarak , bu oldukça basittir:

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

Benim durumumda, içerik türü yanıt başlıkları Content-Type: text / plain idi . Bu yüzden, Buffer'daki verileri şöyle okudum:

let data = [];
stream.on('data', (chunk) => {
 console.log(Buffer.from(chunk).toString())
 data.push(Buffer.from(chunk).toString())
});
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.