Node.jsストリームの内容を文字列変数に読み込むにはどうすればよいですか?


113

smtp-protocolSMTPメールをキャプチャしてメールデータを操作するために使用するNodeプログラムをハッキングしています。ライブラリはメールデータをストリームとして提供しますが、それを文字列に変換する方法がわかりません。

現在はでstdoutに書き込んでstream.pipe(process.stdout, { end: false })いますが、前述したように、ストリームデータが文字列である必要があります。これは、ストリームが終了したら使用できます。

Node.jsストリームからすべてのデータを文字列に収集するにはどうすればよいですか?


ストリームをコピーするか、(autoClose:false)でフラグを立てる必要があります。メモリを汚染することは悪い習慣です。
2013年

回答:


41

(この回答は、何年も前の最良の回答でした。これより下の方に適切な回答があります。node.jsに追いついていません。この質問で「正しい」とマークされているため、この回答を削除できません。 「ダウンクリックを考えている場合は、私に何をしてほしいですか?)

重要なのは、読み取り可能なストリームのdataおよびendイベントを使用することです。これらのイベントを聞いてください:

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

dataイベントを受け取ったら、データを収集するために作成されたバッファーに新しいデータのチャンクを追加します。

endイベントを受け取ったら、必要に応じて、完了したバッファを文字列に変換します。次に、それに必要なことを行います。


149
APIでリンクをポイントするだけではなく、答えを示す数行のコードが望ましいです。答えに反対しないでください、それが十分に完全であると信じてはいけません。
arcseldon 2014年

3
より新しいnode.jsバージョンでは、これはよりクリーンです:stackoverflow.com/a/35530615/271961
Simon A. Eugster

回答は、Promisesライブラリの使用を推奨せず、ネイティブのPromisesを使用するように更新する必要があります。
Dan Dascalescu

@DanDascalescu私はあなたに同意します。問題は、7年前にこの回答を書いていて、node.jsに追いついていないことです。あなたが他の誰かがそれを更新したいのであれば、それは素晴らしいことです。または、私は単にそれを削除することもできます。すでにより良い答えがあるようです。あなたは何をお勧めします?
ControlAltDel

@ControlAltDel:もはや最善ではない回答を削除するあなたの率先に感謝します。他の人も同様の規律を持っていることを望みます。
Dan Dascalescu

129

別の方法は、ストリームをpromiseに変換し(以下の例を参照)、解決された値を変数に割り当てるthen(またはawait)ことです。

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)

ストリームとプロミスは本当に初めてで、次のエラーが出ますSyntaxError: await is only valid in async function。何が悪いのですか?
JohnK

非同期関数内でstreamtostring関数を呼び出す必要があります。これを回避するには、次のこともできますstreamToString(stream).then(function(response){//Do whatever you want with response});
Enclo Creations

23
これが一番の答えになるはずです。(1)チャンクをバッファとして保存.toString("utf8")し、最後に呼び出すだけで、すべてが正しくなる唯一のソリューションを作成して、チャンクがマルチバイト文字の途中で分割された場合のデコード失敗の問題を回避しました。(2)実際のエラー処理。(3)コードを関数に配置して、コピーして貼り付けるのではなく、再利用できるようにします。(4)Promiseを使用して、関数をawaitオンにすることができます。(5)特定のnpmライブラリとは異なり、100万の依存関係をドラッグしない小さなコード。(6)ES6の構文と最新のベストプラクティス。
MultiplyByZer0

チャンク配列をpromiseに移動してみませんか?
ジェニーオライリー

1
ヒントとして現在のトップアンサーを使用して基本的に同じコードを思いついた後Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type string、ストリームがのstring代わりにチャンクを生成する場合、上記のコードが失敗する可能性があることに気付きましたBuffer。使用chunks.push(Buffer.from(chunk))の両方で動作するはずですstringし、Bufferチャンク。
Andrei LED

67

上記のどれもうまくいきませんでした。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
これは実際には最もクリーンな方法です;)
Ivo

7
よく働く。注:適切な文字列型が必要な場合は、concat()呼び出しからの結果のBufferオブジェクトで.toString()を呼び出す必要があります
Bryan Johnson

64

これが上記の答えよりも役立つことを願っています:

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

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

文字列連結は、文字列部分を収集するための最も効率的な方法ではありませんが、簡単にするために使用されていることに注意してください(おそらくコードは効率を気にしません)。

また、このコードは、ASCII以外のテキストに対して予期しないエラーを発生させる可能性があります(すべての文字が1バイトに収まると想定しています)が、おそらくそのことも気にしません。


4
文字列部分を収集するより効率的な方法は何でしょうか?TY
sean2078 2015

2
バッファdocs.nodejitsu.com/articles/advanced/buffers/how-to-use-buffersを使用することもできますが、これは実際の使用状況によって異なります。
Tom Carchrae、2015

2
文字列の配列を使用して、新しいチャンクを配列に追加join("")し、最後に配列を呼び出します。
ValeriuPaloş16年

14
これは正しくありません。バッファがマルチバイトコードポイントの途中にある場合、toString()は不正な形式のutf-8を受け取り、文字列に の束ができてしまいます。
alextgordon 2016年

2
@alextgordonは正しいです。非常にまれなケースで、チャンクがたくさんある場合、チャンクの最初と最後にそれらを取得しました。特に、ロシアのシンボルが端にある場合。したがって、チャンクを変換して連結するのではなく、チャンクを連結して最後に変換するのが適切です。私の場合、リクエストは、デフォルトのエンコーディングを使用したrequest.jsを使用して、あるサービスから別のサービスに行われました
Mike Yermolayev

21

私は通常、この単純な関数を使用して、ストリームを文字列に変換します。

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

使用例:

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

1
便利な答えですが、配列にプッシュする前に各チャンクを文字列に変換する必要があるようです:chunks.push(chunk.toString());
Nicolas Le Thierry d'Ennequin

1
これは私のために働いた唯一のものです!大感謝
538ROMEO

1
これは素晴らしい答えでした!
Aft3rL1f3

12

さらに、promiseを使用した文字列のもう1つ:

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

使用法:

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

.toString()必要に応じて、バイナリデータで使用するを削除します。

update:@AndreiLEDは、これが文字列に問題があることを正しく指摘しました。私が持っているノードのバージョンで文字列を返すストリームを取得できませんでしたが、APIはこれが可能であると指摘しています。


Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringストリームがのstring代わりにチャンクを生成する場合、上記のコードが失敗する可能性があることに気づきましたBuffer。使用chunks.push(Buffer.from(chunk))の両方で動作するはずですstringし、Bufferチャンク。
Andrei LED

良い点、私は答えを更新しました。ありがとう。
エスタニ

8

nodejsのドキュメントからこれを行う必要があります-エンコーディングが単なるバイトの集まりであることを知らずに、常に文字列を覚えてください:

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

ストリームには単純な.toString()関数(私が理解している)や、.toStringAsync(cb)関数の(私が理解していない)もありません。

だから私は自分のヘルパー関数を作成しました:

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

私はそのように使ってもっと幸運でした:

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

私は、ノードを使用v9.11.1してreadstreamからの応答であるhttp.getコールバック。


3

最もクリーンな解決策は、「string-stream」パッケージを使用することです。これは、ストリームをプロミスで文字列に変換します。

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

人気のある(毎週500万回以上のダウンロード)軽量の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

ストリームレデューサーのようなものはどうですか?

以下は、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

これは私のために働き、Node v6.7.0 docsに基づいています:

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

上記のセバスチャンJさん、よくできました。

数行のテストコードで "バッファの問題"が発生し、エンコーディング情報を追加して解決しました。以下を参照してください。

問題を実証する

ソフトウェア

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

入力

hello world

出力

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

ソリューションを実証する

ソフトウェア

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

入力

hello world

出力

string hello world

1

リストされたすべての回答は、読み取り可能なストリームをフローモードで開くように見えます。これはNodeJSのデフォルトではなく、NodeJSが一時停止の読み取り可能なストリームモードで提供するバックプレッシャーサポートがないため、制限がある場合があります。これは、Just Buffers、ネイティブストリーム、ネイティブストリーム変換を使用した実装と、オブジェクトモードのサポートです。

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

これについてどう思う ?

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

動作し、非常にクリーンで、依存関係がありません。
ViRuSTriNiTy

0

プロジェクトの依存関係におそらくすでにある非常に人気のあるstream-buffersパッケージを使用すると、これは非常に簡単です。

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

私の場合、コンテンツタイプの応答ヘッダーはContent-Type:text / plainでした。だから、私はバッファからデータを読んだ:

let data = [];
stream.on('data', (chunk) => {
 console.log(Buffer.from(chunk).toString())
 data.push(Buffer.from(chunk).toString())
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.