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

229

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


你应该复制流或使用(autoClose: false)标记。污染内存是不好的实践。 - 19h
21个回答

372

另一种方法是将流转换为一个promise(请参考下面的示例),并使用then(或await)将已解决的值分配给变量。

function streamToString (stream) {
  const chunks = [];
  return new Promise((resolve, reject) => {
    stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
    stream.on('error', (err) => reject(err));
    stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
  })
}

const result = await streamToString(stream)

4
你需要在一个异步函数内调用streamToString函数。为了避免这个问题,你也可以这样做:streamToString(stream).then(function(response){//对response进行任何操作}); - Enclo Creations
87
恭喜你提供了唯一一个完美解决方案,这应该是最佳答案。你存储块时使用 Buffers,并仅在最后调用 .toString("utf8"),避免了当块在多字节字符中间分割时可能出现的解码错误问题;实现了真正的错误处理;将代码放在一个函数中,以便重复使用,而不是复制粘贴;使用 Promises,因此可以等待函数执行完毕;代码量小,不像某些 npm 库那样拖累了数百个依赖项;使用了 ES6 语法和现代最佳实践。 - MultiplyByZer0
2
为什么不将chunks数组移入Promise中? - Krisztián Balla
5
在参考当前最佳答案的提示后,我想出了基本相同的代码,但我注意到上面的代码可能会在流生成“字符串”块而不是“Buffer”块时失败,并报错为“Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type string”。使用chunks.push(Buffer.from(chunk))应该适用于“string”和“Buffer”块。 - Andrei LED
9
原文:Turns out the actual best answer came late to the party: https://dev59.com/r2kv5IYBdhLWcg3wZwC-#63361543翻译:事实证明,真正最佳的答案在晚些时候才出现了:https://dev59.com/r2kv5IYBdhLWcg3wZwC-#63361543。 - Rafael Beckel
显示剩余6条评论

103

你对此有何看法?

async function streamToString(stream) {
    // lets have a ReadableStream as a stream variable
    const chunks = [];

    for await (const chunk of stream) {
        chunks.push(Buffer.from(chunk));
    }

    return Buffer.concat(chunks).toString("utf-8");
}


5
必须使用chunks.push(Buffer.from(chunk));来处理字符串块以使其正常工作。 - Jan
2
哇,这看起来非常整洁!除了上面评论中提到的问题之外,它还有其他问题吗?它能处理错误吗? - ban_javascript
3
这是现代版的最佳答案。Node.js/JS 变化很快。我建议使用这个而不是排名第一的,因为它更加简洁,不需要用户触发事件。 - Epic Speedy
1
@DirkSchumacher 你的IDE使用过时的脚本解释器(for await是有效的ECMAScript语法),或者如果它尝试(不成功地)执行包含for await的代码,则IDE本身已经过时。这是哪个IDE?无论如何,IDE并不是为了实际在生产中运行程序而设计的,它们会在开发期间对程序进行lint和分析。 - Armen Michaeli
1
@DirkSchumacher 不用担心。只需查找您的IDE确切加载和执行包含“for await”的脚本的组件,我假设它将是一个程序。查询程序版本并确定该版本是否实际支持语法。然后找出为什么您的IDE正在使用特定的“过时”程序版本,并找到更新两者的方法。 - Armen Michaeli
显示剩余5条评论

87

以上方法均不适用于我。我需要使用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));
  });

8
这实际上是最干净的做法 ;) - ivoputzer
11
非常好用。需要注意的是,如果您想要一个正确的字符串类型,您需要在concat()调用返回的Buffer对象上调用.toString()方法。 - Bryan Johnson
5
结果证明,真正最佳的答案是迟到的:https://dev59.com/r2kv5IYBdhLWcg3wZwC-#63361543 - Rafael Beckel
这是唯一正确的做法。 - SaidAkh

67
希望这比上面的答案更有用:
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
有没有更有效的方法来收集字符串的部分?谢谢。 - sean2078
2
你可以使用缓冲区 https://docs.nodejitsu.com/articles/advanced/buffers/how-to-use-buffers ,但这取决于你的使用情况。 - Tom Carchrae
2
使用字符串数组,在数组中追加每个新块,并在最后调用 join("") - Valeriu Paloş
21
如果缓冲区在一个多字节编码点的中间,那么使用 toString() 方法将会得到非法的 UTF-8 字符,结果是你的字符串中会出现很多 �。 - alextgordon
我不确定你所说的“多字节代码点”是什么意思,但如果你想转换流的编码,你可以像这样传递一个编码参数 toString('utf8') - 但默认的字符串编码是 utf8,所以我怀疑你的流可能不是 utf8 @alextgordon - 参见 https://dev59.com/hGct5IYBdhLWcg3wZcmL 获取更多信息。 - Tom Carchrae
3
@alextgordon是对的。在我有很多块时,在某些非常罕见的情况下,我会在块的开头和结尾得到那些�符号。特别是当边缘有俄语符号时。因此,正确的做法是连接块并在结束时将它们转换,而不是先转换块再将它们连接起来。在我的情况下,请求是使用request.js从一个服务向另一个服务发送的,并且使用默认编码。 - Mike Yermolayev

58

(这个答案来自多年前,当时是最好的答案。现在下面有更好的答案。我没有跟上node.js的更新,但我不能删除这个答案,因为它被标记为“正确答案”。如果您正在考虑投票下降,请告诉我该怎么做?)

关键是使用dataend事件的可读流。监听这些事件:

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

在接收到 data 事件时,将新的数据块添加到一个创建用于收集数据的 Buffer 中。

在接收到 end 事件时,如果需要,将完成的 Buffer 转换为字符串。然后进行必要的操作。


171
提供几行代码来说明答案比简单地在API上提供链接更好。不要反对答案,只是认为它不够完整。 - arcseldon
4
使用更新的node.js版本,这会更简洁:https://dev59.com/r2kv5IYBdhLWcg3wZwC-#35530615 - Simon A. Eugster
答案应该更新,不建议使用 Promises 库,而是使用原生的 Promises。 - Dan Dascalescu
@DanDascalescu 我同意你的观点。问题在于我写下这个答案已经有7年了,而且我没有跟上node.js的最新动态。如果你或其他人想要更新它,那就太好了。或者我也可以将其删除,因为似乎已经有更好的答案了。你会推荐什么呢? - ControlAltDel
结果证明,实际上最好的答案来得比较晚:https://dev59.com/r2kv5IYBdhLWcg3wZwC-#63361543 - Rafael Beckel
显示剩余2条评论

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
这是唯一一个对我有效的!非常感谢。 - TOPKAT
1
这是一个很棒的答案! - JΛYDΞV
1
这里存在一个边缘情况,即当多字节字符被分割在块之间时。这会导致原始字符被替换为两个不正确的字符。 - PhilB

15

还有一个使用 promises 处理字符串的示例:

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正确指出这对字符串存在问题。我无法在我使用的node版本中获得返回字符串的流,但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

9

使用流行的(每周下载量超过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
})();

8
从Node.js的文档中可以得知,你应该这样做 - 要记住,如果不知道编码方式,那么一个字符串只是一堆字节。
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);
})

5
我尝试过以下方式,效果更好:

我曾经用过以下方法:

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

我使用的是 Node v9.11.1 版本,readstream 是从一个 http.get 回调函数中返回的响应。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接