ฉันจะอ่านเนื้อหาของสตรีม Node.js ในตัวแปรสตริงได้อย่างไร


114

ฉันกำลังแฮ็กโปรแกรม Node ที่ใช้smtp-protocolดักจับอีเมล SMTP และดำเนินการกับข้อมูลอีเมล ไลบรารีให้ข้อมูลเมลเป็นสตรีมและฉันไม่รู้ว่าจะเอาข้อมูลนั้นไปเป็นสตริงได้อย่างไร

ฉันกำลังเขียนมันเป็น stdout ด้วยstream.pipe(process.stdout, { end: false })แต่อย่างที่บอกฉันต้องการข้อมูลสตรีมเป็นสตริงแทนซึ่งฉันสามารถใช้ได้เมื่อสตรีมสิ้นสุดลง

ฉันจะรวบรวมข้อมูลทั้งหมดจากสตรีม Node.js เป็นสตริงได้อย่างไร


คุณควรคัดลอกสตรีมหรือตั้งค่าสถานะด้วย (autoClose: false) เป็นการปฏิบัติที่ไม่ดีในการสร้างมลพิษให้กับหน่วยความจำ
19h

คำตอบ:


42

(คำตอบนี้มาจากปีที่แล้วซึ่งเป็นคำตอบที่ดีที่สุดตอนนี้มีคำตอบที่ดีกว่าอยู่ด้านล่างนี้ฉันไม่ได้ใช้ node.js และฉันไม่สามารถลบคำตอบนี้ได้เนื่องจากมีเครื่องหมาย "ถูกต้องสำหรับคำถามนี้ ". ถ้าคุณคิดจะคลิกลงคุณต้องการให้ฉันทำอย่างไร?)

กุญแจสำคัญคือการใช้dataและendเหตุการณ์ของสตรีมที่อ่านได้ ฟังเหตุการณ์เหล่านี้:

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

เมื่อคุณได้รับdataเหตุการณ์ให้เพิ่มกลุ่มข้อมูลใหม่ลงในบัฟเฟอร์ที่สร้างขึ้นเพื่อรวบรวมข้อมูล

เมื่อคุณได้รับendเหตุการณ์ให้แปลง Buffer ที่เสร็จสมบูรณ์เป็นสตริงหากจำเป็น จากนั้นทำสิ่งที่คุณต้องทำกับมัน


155
โค้ดสองสามบรรทัดที่แสดงคำตอบนั้นดีกว่าเพียงแค่ชี้ลิงก์ไปที่ API อย่าเห็นด้วยกับคำตอบอย่าเชื่อว่ามันสมบูรณ์เพียงพอแล้ว
arcseldon

3
ด้วยรุ่น node.js ที่ใหม่กว่านี้จะสะอาดกว่า: stackoverflow.com/a/35530615/271961
Simon A. Eugster

คำตอบควรได้รับการอัปเดตเพื่อไม่แนะนำให้ใช้ไลบรารีสัญญา แต่ใช้คำสัญญาดั้งเดิม
Dan Dascalescu

@DanDascalescu ฉันเห็นด้วยกับคุณ ปัญหาคือฉันเขียนคำตอบนี้เมื่อ 7 ปีที่แล้วและฉันไม่ได้ติดตาม node.js หากคุณเป็นคนอื่นที่ต้องการอัปเดตก็จะดีมาก หรือฉันสามารถลบมันได้เพราะดูเหมือนจะมีคำตอบที่ดีกว่าอยู่แล้ว คุณจะแนะนำอะไร?
ControlAltDel

@ControlAltDel: ฉันขอขอบคุณความคิดริเริ่มของคุณในการลบคำตอบที่ไม่ดีที่สุดอีกต่อไป อยากให้คนอื่น ๆ ที่คล้ายกันมีระเบียบวินัย
Dan Dascalescu

134

อีกวิธีหนึ่งคือการแปลงสตรีมเป็นสัญญา (ดูตัวอย่างด้านล่าง) และใช้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 ภายในฟังก์ชัน async เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถทำได้เช่นกันstreamToString(stream).then(function(response){//Do whatever you want with response});
Creations

27
นี่น่าจะเป็นคำตอบอันดับต้น ๆ ขอแสดงความยินดีกับการผลิตโซลูชันเดียวที่ทำให้ทุกอย่างถูกต้องโดย (1) จัดเก็บชิ้นส่วนเป็นบัฟเฟอร์และเรียกเฉพาะ.toString("utf8")ในตอนท้ายเพื่อหลีกเลี่ยงปัญหาความล้มเหลวในการถอดรหัสหากมีการแยกชิ้นส่วนที่อยู่ตรงกลางของอักขระแบบหลายไบต์ (2) การจัดการข้อผิดพลาดที่เกิดขึ้นจริง (3) ใส่รหัสในฟังก์ชันเพื่อให้สามารถใช้ซ้ำได้ไม่ใช่คัดลอกวาง (4) การใช้ทองคำเพื่อฟังก์ชั่นที่สามารถawait-ed บน; (5) รหัสขนาดเล็กที่ไม่ลากในการอ้างอิงเป็นล้านซึ่งแตกต่างจากไลบรารี npm บางแห่ง (6) ไวยากรณ์ ES6 และแนวทางปฏิบัติที่ดีที่สุดสมัยใหม่
MultiplyByZer0

ทำไมไม่ย้ายอาร์เรย์ชิ้นเป็นสัญญา?
Jenny O'Reilly

2
หลังจากที่ผมขึ้นมาด้วยหลักรหัสเดียวกันโดยใช้คำตอบด้านบนปัจจุบันเป็นคำใบ้ที่ฉันได้สังเกตเห็นว่าโค้ดข้างต้นอาจล้มเหลวด้วย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

ข้างต้นไม่ได้ผลสำหรับฉัน ฉันต้องการใช้วัตถุบัฟเฟอร์:

  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
ใช้งานได้ดี หมายเหตุ: หากคุณต้องการประเภทสตริงที่ถูกต้องคุณจะต้องเรียก. toString () บนวัตถุบัฟเฟอร์ที่เป็นผลลัพธ์จากการโทร concat ()
ไบรอันจอห์นสัน

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 ได้แต่ขึ้นอยู่กับการใช้งานของคุณจริงๆ
Tom Carchrae

2
ใช้อาร์เรย์ของสตริงที่คุณต่อท้ายแต่ละกลุ่มใหม่เข้ากับอาร์เรย์และเรียกjoin("")อาร์เรย์ในตอนท้าย
Valeriu Paloş

14
นี่ไม่ถูกต้อง หากบัฟเฟอร์อยู่ครึ่งทางของจุดโค้ดแบบหลายไบต์ toString () จะได้รับ utf-8 ที่ผิดรูปแบบและคุณจะจบลงด้วยพวง ในสตริงของคุณ
alextgordon

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))ควรใช้ได้กับทั้งสอง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

วิธีแก้ปัญหาที่สะอาดที่สุดอาจใช้แพ็กเกจ "สตริงสตรีม" ซึ่งจะแปลงสตรีมเป็นสตริงโดยมีคำมั่นสัญญา

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

วิธีง่ายๆด้วยยอดนิยม (ดาวน์โหลดมากกว่า 5 ล้านครั้งต่อสัปดาห์) และไลบรารีรับสตรีมน้ำหนักเบา:

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

3

คุณคิดอย่างไรเกี่ยวกับเรื่องนี้ ?

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

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 :

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

คำตอบทั้งหมดในรายการดูเหมือนจะเปิด Readable Stream ในโหมดการไหลซึ่งไม่ใช่ค่าเริ่มต้นใน NodeJS และอาจมีข้อ จำกัด เนื่องจากไม่มีการรองรับแรงดันย้อนกลับที่ NodeJS ให้ไว้ในโหมดสตรีมที่อ่านได้หยุดชั่วคราว นี่คือการนำไปใช้งานโดยใช้ Just Buffers, Native Stream และ Native Stream Transforms และรองรับ Object Mode

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 ดังนั้นฉันได้อ่านข้อมูลจาก Buffer เช่น:

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.