Как мне прочитать содержимое потока Node.js в строковой переменной?


113

Я взламываю программу Node, которая использует smtp-protocolдля захвата электронной почты SMTP и обработки почтовых данных. Библиотека предоставляет данные почты в виде потока, и я не знаю, как преобразовать это в строку.

В настоящее время я пишу его в stdout с помощью stream.pipe(process.stdout, { end: false }), но, как я уже сказал, мне нужны данные потока в строке, которую я могу использовать после завершения потока.

Как мне собрать все данные из потока Node.js в строку?


Вы должны скопировать поток или пометить его с помощью (autoClose: false). Загрязнять память - плохая практика.
19

Ответы:


41

(Этот ответ был дан много лет назад, когда это был лучший ответ. Теперь под ним есть лучший ответ. Я не следил за node.js и не могу удалить этот ответ, потому что он помечен как "правильный по этому вопросу ". Если вы думаете о щелчке вниз, что вы хотите, чтобы я сделал?)

Ключ должен использовать dataи endсобытия в Readable потока . Послушайте эти события:

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

Когда вы получите dataсобытие, добавьте новый блок данных в буфер, созданный для сбора данных.

При получении endсобытия преобразуйте заполненный буфер в строку, если необходимо. Затем сделайте с ним то, что вам нужно.


151
Пара строк кода, иллюстрирующих ответ, предпочтительнее, чем просто указывать ссылку на API. Не соглашайтесь с ответом, просто не верьте, что он достаточно полный.
arcseldon

3
С более новыми версиями node.js это чище: stackoverflow.com/a/35530615/271961
Саймон А. Эугстер,

Ответ следует обновить, чтобы не рекомендовать использовать библиотеку Promises, а использовать собственные Promises.
Дэн Даскалеску

@DanDascalescu Я согласен с вами. Проблема в том, что я написал этот ответ 7 лет назад и не успеваю за node.js. Если вы кто-то другой хотел бы его обновить, это было бы здорово. Или я мог бы просто удалить его, поскольку, кажется, уже есть лучший ответ. Что бы вы порекомендовали?
ControlAltDel

@ControlAltDel: Я ценю вашу инициативу по удалению ответа, который уже не является лучшим. Желаю, чтобы у других была такая же дисциплина .
Дэн Даскалеску

129

Другой способ - преобразовать поток в обещание (см. Пример ниже) и использовать 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) использование обещаний, чтобы функция могла быть awaitвключена; (5) небольшой код, который не затягивает миллион зависимостей, в отличие от некоторых библиотек npm; (6) Синтаксис ES6 и современные передовые практики.
MultiplyByZer0

Почему бы не переместить массив chunks в обещание?
Дженни О'Рейли

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фрагментами.
Андрей 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
на самом деле это самый чистый способ сделать это;)
Иво

7
Прекрасно работает. Просто примечание: если вам нужен правильный тип строки, вам нужно будет вызвать .toString () для полученного объекта Buffer из вызова 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("")массив в конце.
Валериу Палош

14
Это неправильно. Если буфер находится на полпути через многобайтовую кодовую точку, то toString () получит искаженный utf-8, и вы получите кучу в вашей строке.
alextgordon

2
@alextgordon прав. В очень редких случаях, когда у меня было много кусков, я получал их - в начале и в конце кусков. Особенно когда там русские символы по краям. Поэтому правильно объединять куски и преобразовывать их в конце вместо преобразования кусков и их объединения. В моем случае запрос был сделан из одной службы в другую с помощью request.js с кодировкой по умолчанию
Майк Ермолаев

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());
помещен

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фрагментами.
Андрей 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));

Я использую узел, 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

Простой способ с популярной (более 5 млн загрузок в неделю) и легкой библиотекой 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 :

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

Молодец, Себастьян Дж. Выше.

У меня была «проблема с буфером» с несколькими строками тестового кода, которые у меня были, я добавил информацию о кодировке и решил ее, см. Ниже.

Продемонстрируйте проблему

программное обеспечение

// 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 предоставляет в режиме Paused Readable Stream Mode. Вот реализация с использованием Just Buffers, Native Stream и Native Stream Transforms и поддержки объектного режима.

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 . Итак, я прочитал данные из Buffer, например:

let data = [];
stream.on('data', (chunk) => {
 console.log(Buffer.from(chunk).toString())
 data.push(Buffer.from(chunk).toString())
});
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.