如何将Node.js流的内容读入字符串变量?


113

我正在入侵smtp-protocol用于捕获SMTP电子邮件并处理邮件数据的Node程序。该库将邮件数据作为流提供,但我不知道如何将其转换为字符串。

我目前正在使用将其写入stdout stream.pipe(process.stdout, { end: false }),但是正如我所说,我需要将流数据存储在字符串中,一旦流结束,就可以使用它。

如何将所有数据从Node.js流收集到字符串中?


您应该复制流或使用(autoClose:false)对其进行标记。污染内存是不好的做法。
2013年

Answers:


41

(这个答案来自几年前的最佳答案。现在,下面有一个更好的答案。我没有跟上node.js的步伐,我无法删除此答案,因为它被标记为“在此问题上正确” “。如果您想点击一下鼠标,您想让我做什么?)

关键是使用Readable Streamdataend事件。听这些事件:

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

收到data事件后,将新的数据块添加到为收集数据而创建的缓冲区中。

收到end事件时,如有必要,将完成的Buffer转换为字符串。然后做你需要做的。


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)将这些块存储为Buffers并且仅.toString("utf8")在最后调用,以避免如果在多字节字符中间分割一个块而导致解码失败的问题;(2)实际错误处理;(3)将代码放入函数中,以便可以重用,而不是复制粘贴;(4)使用Promises,以便可以await启用该功能;(5)与某些npm库不同,它不会拖曳一百万个依赖项的小代码;(6)ES6语法和现代最佳实践。
MultiplyByZer0

为什么不将chunks数组移入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))应该与stringBuffer块一起使用。
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文本产生无法预测的错误(它假定每个字符都适合一个字节),但是也许您也不在乎。


4
有什么更有效的方法来收集琴弦部分?TY
sean2078

2
您可以使用缓冲区docs.nodejitsu.com/articles/advanced/buffers/how-to-use-buffers,但这实际上取决于您的使用。
汤姆·卡克雷

2
使用一个字符串数组,其中将每个新块附加到该数组,并join("")在最后调用该数组。
ValeriuPaloş16年

14
这是不对的。如果缓冲区位于多字节代码点的一半位置,则toString()将收到格式错误的utf-8,并且最终在字符串中会出现一堆 。
alextgordon '16

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

还有一个使用诺言的字符串:

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()如果需要,请删除以与二进制数据一起使用。

更新:@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))应该与stringBuffer块一起使用。
Andrei LED

好点,我已经更新了答案。谢谢。
estani

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

我使用node v9.11.1并且readstream是来自http.get回调的响应。


3

最干净的解决方案可能是使用“字符串流”包,该包将流转换为带有承诺的字符串。

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

上面的Sebastian 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 Buffer,本机流和本机流转换的实现,并支持对象模式

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

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。因此,我已经从Buffer中读取了数据,例如:

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

0

你怎么看待这件事 ?

// 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")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.