Làm cách nào để đọc nội dung của luồng Node.js thành một biến chuỗi?


113

Tôi đang tấn công một chương trình Node sử dụng smtp-protocolđể thu thập các email SMTP và hành động trên dữ liệu thư. Thư viện cung cấp dữ liệu thư dưới dạng một luồng và tôi không biết cách chuyển dữ liệu đó thành một chuỗi.

Tôi hiện đang viết nó cho stdout stream.pipe(process.stdout, { end: false }), nhưng như tôi đã nói, thay vào đó, tôi cần dữ liệu luồng trong một chuỗi, mà tôi có thể sử dụng sau khi luồng kết thúc.

Làm cách nào để thu thập tất cả dữ liệu từ luồng Node.js thành một chuỗi?


Bạn nên sao chép luồng hoặc gắn cờ luồng đó bằng (autoClose: false). Đó là thực hành xấu để làm ô nhiễm bộ nhớ.
19h

Câu trả lời:


41

(Câu trả lời này là từ nhiều năm trước, khi nó là câu trả lời tốt nhất. Hiện tại có một câu trả lời tốt hơn bên dưới câu này. Tôi không theo kịp node.js và tôi không thể xóa câu trả lời này vì nó được đánh dấu là "đúng trên câu hỏi này ". Nếu bạn đang nghĩ đến việc nhấp chuột xuống, bạn muốn tôi làm gì?)

Điều quan trọng là sử dụng dataendcác sự kiện của Luồng có thể đọc . Lắng nghe những sự kiện sau:

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

Khi bạn nhận được datasự kiện, hãy thêm đoạn dữ liệu mới vào Bộ đệm được tạo để thu thập dữ liệu.

Khi bạn nhận được endsự kiện, hãy chuyển đổi Bộ đệm đã hoàn thành thành một chuỗi, nếu cần. Sau đó, làm những gì bạn cần làm với nó.


149
Một vài dòng mã minh họa câu trả lời tốt hơn là chỉ trỏ một liên kết đến API. Đừng không đồng ý với câu trả lời, chỉ cần không tin rằng nó là đủ.
arcseldon

3
Với Node.js phiên bản mới hơn, đây là sạch hơn: stackoverflow.com/a/35530615/271961
Simon A. Eugster

Câu trả lời nên được cập nhật để không khuyên bạn nên sử dụng thư viện Promises mà hãy sử dụng Promises gốc.
Dan Dascalescu

@DanDascalescu Tôi đồng ý với bạn. Vấn đề là tôi đã viết câu trả lời này cách đây 7 năm và tôi đã không theo kịp node.js. Nếu bạn là người khác muốn cập nhật nó, điều đó thật tuyệt. Hoặc tôi có thể chỉ cần xóa nó đi, vì dường như đã có câu trả lời tốt hơn. Bạn muốn giới thiệu điều gì?
ControlAltDel

@ControlAltDel: Tôi đánh giá cao việc bạn chủ động xóa câu trả lời không còn là hay nhất nữa. Ước gì những người khác có kỷ luật tương tự .
Dan Dascalescu

129

Một cách khác là chuyển đổi luồng thành một lời hứa (tham khảo ví dụ bên dưới) và sử dụng then(hoặc await) để gán giá trị đã phân giải cho một biến.

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)

Tôi thực sự mới đến con suối và những lời hứa và tôi nhận được lỗi này: SyntaxError: await is only valid in async function. Tôi đang làm gì sai?
JohnK

Bạn phải gọi hàm streamtostring trong một hàm async. Để tránh điều này bạn cũng có thể làmstreamToString(stream).then(function(response){//Do whatever you want with response});
Enclo Creations

23
Đây phải là câu trả lời hàng đầu. Chúc mừng bạn đã tạo ra giải pháp duy nhất giúp mọi thứ ổn thỏa, với (1) lưu trữ các khối dưới dạng Bộ đệm và chỉ gọi .toString("utf8")ở cuối, để tránh sự cố giải mã không thành công nếu một đoạn được tách ở giữa một ký tự nhiềubyte; (2) xử lý lỗi thực tế; (3) đưa mã vào một hàm, để nó có thể được sử dụng lại, không phải sao chép; (4) sử dụng Promises để có thể bật chức năng await; (5) mã nhỏ không kéo theo một triệu phụ thuộc, không giống như một số thư viện npm; (6) Cú pháp ES6 và các phương pháp hay nhất hiện đại.
MultiplyByZer0

Tại sao không chuyển mảng khối vào phần hứa?
Jenny O'Reilly

1
Sau khi tôi nghĩ ra cùng một mã về cơ bản bằng cách sử dụng câu trả lời hàng đầu hiện tại như gợi ý, tôi nhận thấy rằng mã trên có thể không thành công Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringnếu luồng tạo ra stringcác khối thay vì Buffer. Sử dụng chunks.push(Buffer.from(chunk))phải làm việc với cả hai stringBufferkhối.
Andrei LED

67

Không có điều nào ở trên làm việc cho tôi. Tôi cần sử dụng đối tượng 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
đây thực sự là cách sạch sẽ nhất để làm điều đó;)
Ivo

7
Hoạt động tuyệt vời. Chỉ cần một lưu ý: nếu bạn muốn có một kiểu chuỗi thích hợp, bạn sẽ cần phải gọi ToString () trên kết quả các đối tượng đệm từ concat () gọi
Bryan Johnson

64

Hy vọng điều này hữu ích hơn câu trả lời trên:

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

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

Lưu ý rằng nối chuỗi không phải là cách hiệu quả nhất để thu thập các phần của chuỗi, nhưng nó được sử dụng để đơn giản hóa (và có lẽ mã của bạn không quan tâm đến hiệu quả).

Ngoài ra, mã này có thể tạo ra các lỗi không thể đoán trước cho văn bản không phải ASCII (nó giả định rằng mọi ký tự đều nằm trong một byte), nhưng có lẽ bạn cũng không quan tâm đến điều đó.


4
Cách hiệu quả hơn để thu thập các phần chuỗi là gì? TY
sean2078

2
bạn có thể sử dụng bộ đệm docs.nodejitsu.com/articles/advanced/buffers/how-to-use-buffers nhưng nó thực sự phụ thuộc vào cách sử dụng của bạn.
Tom Carchrae

2
Sử dụng một mảng chuỗi trong đó bạn nối từng đoạn mới vào mảng và gọi join("")mảng ở cuối.
Valeriu Paloş

14
Điều này không đúng. Nếu bộ đệm đi qua nửa điểm mã nhiều byte thì toString () sẽ nhận được utf-8 không đúng định dạng và bạn sẽ kết thúc với một loạt trong chuỗi của mình.
alextgordon 16/10/16

2
@alextgordon nói đúng. Trong một số trường hợp rất hiếm khi tôi có nhiều phần, tôi nhận được những phần đó ở đầu và cuối các phần. Đặc biệt là khi có biểu tượng tiếng Nga trên các cạnh. Vì vậy, việc nối các đoạn và chuyển đổi chúng cuối cùng là đúng thay vì chuyển đổi các đoạn và nối chúng. Trong trường hợp của tôi, yêu cầu được thực hiện từ dịch vụ này sang dịch vụ khác bằng request.js với mã hóa mặc định
Mike Yermolayev

21

Tôi thường sử dụng hàm đơn giản này để chuyển đổi một luồng thành một chuỗi:

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

Ví dụ sử dụng:

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

1
Câu trả lời hữu ích nhưng có vẻ như mỗi đoạn phải được chuyển đổi sang một chuỗi trước khi nó được đẩy vào các mảng:chunks.push(chunk.toString());
Nicolas Lê Thierry d'Ennequin

1
Đây là cái duy nhất làm việc cho tôi! Cảm ơn rất nhiều
538ROMEO

1
Đây là một câu trả lời tuyệt vời!
Aft3rL1f3

12

Và một cái khác cho chuỗi sử dụng lời hứa:

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

Sử dụng:

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

loại bỏ .toString()để sử dụng với Dữ liệu nhị phân nếu cần.

cập nhật : @AndreiLED đã chỉ ra chính xác điều này có vấn đề với chuỗi. Tôi không thể nhận được luồng trả về chuỗi với phiên bản nút mà tôi có, nhưng api lưu ý rằng điều này là có thể.


Tôi đã nhận thấy rằng mã trên có thể không thành công Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type stringnếu luồng tạo ra stringcác khối thay vì Buffer. Sử dụng chunks.push(Buffer.from(chunk))phải làm việc với cả hai stringBufferkhối.
Andrei LED

điểm tốt, tôi đã cập nhật câu trả lời. Cảm ơn.
estani

8

Từ tài liệu nodejs, bạn nên làm điều này - luôn nhớ một chuỗi mà không biết mã hóa chỉ là một loạt các 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);
})

6

Luồng không đơn giản .toString() chức năng (mà tôi hiểu) cũng như một cái gì đó giống như một .toStringAsync(cb)chức năng (mà tôi không hiểu).

Vì vậy, tôi đã tạo hàm trợ giúp của riêng mình:

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

Tôi đã gặp nhiều may mắn hơn khi sử dụng như vậy:

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

Tôi sử dụng nút v9.11.1và đó readstreamlà phản hồi từ một http.getcuộc gọi lại.


3

Giải pháp rõ ràng nhất có thể là sử dụng gói "chuỗi-luồng", gói này chuyển đổi một luồng thành một chuỗi với một lời hứa.

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

Dễ dàng với phổ biến (hơn 5 triệu lượt tải xuống hàng tuần) và nhẹ get-stream thư viện:

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

Điều gì về một cái gì đó giống như một trình giảm luồng?

Đây là một ví dụ sử dụng các lớp ES6 cách sử dụng một.

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

Điều này phù hợp với tôi và dựa trên tài liệu 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);
})

1

setEncoding ('utf8');

Làm tốt Sebastian J ở trên.

Tôi đã gặp "sự cố bộ đệm" với một vài dòng mã kiểm tra mà tôi có, và thêm thông tin mã hóa và nó đã giải quyết được, hãy xem bên dưới.

Chứng minh vấn đề

phần mềm

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

đầu vào

hello world

đầu ra

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

Chứng minh giải pháp

phần mềm

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

đầu vào

hello world

đầu ra

string hello world

1

Tất cả các câu trả lời được liệt kê dường như mở Luồng có thể đọc ở chế độ đang chảy, đây không phải là mặc định trong NodeJS và có thể có những hạn chế vì nó thiếu hỗ trợ áp suất ngược mà NodeJS cung cấp trong Chế độ luồng có thể đọc được tạm dừng. Đây là cách triển khai sử dụng Just Buffers, Native Stream và Native Stream Transforms và hỗ trợ cho Chế độ đối tượng

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

Sử dụng gói khá phổ biếnstream-buffers mà bạn có thể đã có trong các gói phụ thuộc dự án của mình, điều này khá đơn giản:

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

Trong trường hợp của tôi, tiêu đề phản hồi loại nội dung là Loại nội dung : văn bản / đơn giản . Vì vậy, tôi đã đọc dữ liệu từ Buffer như:

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

0

Bạn nghĩ gì về điều này ?

// 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")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.