Como criar fluxos de string no Node.Js?

177

Estou usando uma biblioteca, ya-csv , que espera um arquivo ou um fluxo como entrada, mas tenho uma string.

Como faço para converter essa string em um fluxo no nó?

pathikrit
fonte

Respostas:

27

No nó 10.17, stream.Readable possui um frommétodo para criar facilmente fluxos a partir de qualquer iterável (que inclui literais de matriz):

const { Readable } = require("stream")

const readable = Readable.from(["input string"])

readable.on("data", (chunk) => {
  console.log(chunk) // will be called once with `"input string"`
})

Observe que pelo menos entre 10.17 e 12.3, uma sequência é iterável, portanto Readable.from("input string")funcionará, mas emitirá um evento por caractere. Readable.from(["input string"])emitirá um evento por item na matriz (nesse caso, um item).

Observe também que nos nós posteriores (provavelmente 12.3, já que a documentação diz que a função foi alterada), não é mais necessário agrupar a sequência em uma matriz.

https://nodejs.org/api/stream.html#stream_stream_readable_from_iterable_options

Fizker
fonte
De acordo com stream.Readable.from , Calling Readable.from (string) ou Readable.from (buffer) não fará com que as seqüências de caracteres ou buffers sejam iterados para corresponder à semântica de outros fluxos por motivos de desempenho.
abbr
Foi mal. A função foi adicionada na versão 10.7 e se comportou da maneira que descrevi originalmente. Algum tempo depois, as seqüências de caracteres não precisam mais ser agrupadas em matrizes (desde a versão 12.3, ele não itera mais cada caractere individualmente).
Fizker
186

Como o @substack me corrigiu no #node , a nova API de fluxos no nó v10 facilita isso:

const Readable = require('stream').Readable;
const s = new Readable();
s._read = () => {}; // redundant? see update below
s.push('your text here');
s.push(null);

… Após o qual você pode canalizá- lo ou passá-lo livremente para o consumidor pretendido.

Não é tão limpo quanto o one-liner do resumerador , mas evita a dependência extra.

( Atualização: das v0.10.26 à v9.2.1 até agora, uma chamada para pushdiretamente do prompt REPL falhará com uma not implementedexceção se você não tiver definido _read. Não falhará dentro de uma função ou script. Se a inconsistência o fizer nervoso, inclua o noop.)

Garth Kidd
fonte
6
Nos documentos (link) : "Todas as implementações de fluxo legíveis devem fornecer um _readmétodo para buscar dados do recurso subjacente".
Felix Rabe
2
@eye_mew você precisa solicitar ('stream') primeiro
Jim Jones
8
Por que você pressiona nullo buffer do fluxo?
dopatraman 23/02
5
@dopatraman nulldiz ao stream que terminou de ler todos os dados e fechá
chrishiestand
2
Parece que você não deveria fazer assim. Citando os documentos : "O readable.push()método deve ser chamado apenas pelos implementadores legíveis e somente de dentro do readable._read()método".
Axel Rauschmayer 06/04
127

Não use a resposta de Jo Liss. Funcionará na maioria dos casos, mas no meu caso, perdi uma boa localização de bug de 4 ou 5 horas. Não há necessidade de módulos de terceiros para fazer isso.

NOVA RESPOSTA :

var Readable = require('stream').Readable

var s = new Readable()
s.push('beep')    // the string you want
s.push(null)      // indicates end-of-file basically - the end of the stream

Este deve ser um fluxo legível totalmente compatível. Veja aqui para mais informações sobre como usar fluxos corretamente.

RESPOSTA ANTIGA : Basta usar o fluxo PassThrough nativo:

var stream = require("stream")
var a = new stream.PassThrough()
a.write("your string")
a.end()

a.pipe(process.stdout) // piping will work as normal
/*stream.on('data', function(x) {
   // using the 'data' event works too
   console.log('data '+x)
})*/
/*setTimeout(function() {
   // you can even pipe after the scheduler has had time to do other things
   a.pipe(process.stdout) 
},100)*/

a.on('end', function() {
    console.log('ended') // the end event will be called properly
})

Observe que o evento 'close' não é emitido (o que não é requerido pelas interfaces de fluxo).

BT
fonte
2
@Finn Você não precisa os parênteses em javascript, se não existem quaisquer argumentos
BT
não use "var" em 2018! mas const
stackdave
30

Basta criar uma nova instância do streammódulo e personalizá-lo de acordo com suas necessidades:

var Stream = require('stream');
var stream = new Stream();

stream.pipe = function(dest) {
  dest.write('your string');
  return dest;
};

stream.pipe(process.stdout); // in this case the terminal, change to ya-csv

ou

var Stream = require('stream');
var stream = new Stream();

stream.on('data', function(data) {
  process.stdout.write(data); // change process.stdout to ya-csv
});

stream.emit('data', 'this is my string');
zemirco
fonte
13
Esse código quebra as convenções de fluxo. pipe()deve retornar o fluxo de destino, pelo menos.
greim
2
O evento final não é chamado se você usar esse código. Essa não é uma boa maneira de criar um fluxo que possa ser usado em geral.
BT
12

Edit: A resposta de Garth é provavelmente melhor.

Meu texto de resposta antigo é preservado abaixo.


Para converter uma string para um fluxo, você pode usar uma pausa através de stream:

through().pause().queue('your string').end()

Exemplo:

var through = require('through')

// Create a paused stream and buffer some data into it:
var stream = through().pause().queue('your string').end()

// Pass stream around:
callback(null, stream)

// Now that a consumer has attached, remember to resume the stream:
stream.resume()
Jo Liss
fonte
Não consegui que a solução da zeMirco funcionasse no meu caso de uso, mas resumerfuncionou muito bem. Obrigado!
MPEN
A sugestão de resumer @substack funcionou muito bem para mim. Obrigado!
Garth Kidd
2
O resumer é ótimo, mas o "retomar automaticamente o fluxo no nextTick" pode gerar surpresas se você espera que o possa transmitir a consumidores desconhecidos! Eu tinha algum código que canalizava um fluxo de conteúdo para um arquivo se um salvamento em banco de dados de metadados fosse bem-sucedido. Isso foi um bug oculto, aconteceu quando a gravação do db retornou o sucesso imediatamente! Mais tarde refatorei as coisas para estarem dentro de um bloco assíncrono e, boom, o fluxo nunca foi legível. Lição: se você não souber quem consumirá seu fluxo, siga a técnica through (). Pause () .ueue ('string'). End ().
Jolly Roger
1
Passei cerca de 5 horas depurando meu código porque usei a parte de resumerador desta resposta. Seria ótimo se você pudesse gostar .. removê-lo
BT
10

Existe um módulo para isso: https://www.npmjs.com/package/string-to-stream

var str = require('string-to-stream')
str('hi there').pipe(process.stdout) // => 'hi there' 
Lori
fonte
1
Isso é um trocadilho com "existe um aplicativo para isso"? ;)
masterxilo
1
O link no comentário é útil: npmjs.com/package/string-to-stream
Dem Pilafian
Para sua informação, tentei usar esta biblioteca para escrever JSON no Google Drive, mas não funcionou para mim. Escreveu um artigo sobre isso aqui: medium.com/@dupski/… . Também adicionado como resposta abaixo
Russell Briggs
6

no roteiro do café:

class StringStream extends Readable
  constructor: (@str) ->
    super()

  _read: (size) ->
    @push @str
    @push null

use-o:

new StringStream('text here').pipe(stream1).pipe(stream2)
xinthink
fonte
6

Outra solução é passar a função de leitura para o construtor de Readable ( opções legíveis do fluxo de documentos do cf )

var s = new Readable({read(size) {
    this.push("your string here")
    this.push(null)
  }});

após o uso do s.pipe, por exemplo,

Philippe T.
fonte
qual é o propósito do retorno no final?
precisa
"sempre retorne algo (ou nada)", este exemplo da documentação.
Philippe T.
Em JS, se uma função não tem um retorno, é equivalente ao seu retorno vazio. Você poderia fornecer um link onde o encontrou?
precisa
você deveria estar certo. Eu disse isso mais para as melhores práticas. Não quero devolver nada, não é um erro. Então eu removo a linha.
Philippe T.
5

Eu cansei de ter que reaprender isso a cada seis meses, então acabei de publicar um módulo npm para abstrair os detalhes da implementação:

https://www.npmjs.com/package/streamify-string

Este é o núcleo do módulo:

const Readable = require('stream').Readable;
const util     = require('util');

function Streamify(str, options) {

  if (! (this instanceof Streamify)) {
    return new Streamify(str, options);
  }

  Readable.call(this, options);
  this.str = str;
}

util.inherits(Streamify, Readable);

Streamify.prototype._read = function (size) {

  var chunk = this.str.slice(0, size);

  if (chunk) {
    this.str = this.str.slice(size);
    this.push(chunk);
  }

  else {
    this.push(null);
  }

};

module.exports = Streamify;

stré o stringque deve ser passado para o construtor na invocação e será gerado pelo fluxo como dados. optionssão as opções típicas que podem ser passadas para um fluxo, de acordo com a documentação .

De acordo com o Travis CI, ele deve ser compatível com a maioria das versões do nó.

Chris Allen Lane
fonte
2
Quando publiquei isso inicialmente, não incluí o código relevante, que me disseram que é desaprovado.
Chris Allen Lane,
2

Aqui está uma solução organizada no TypeScript:

import { Readable } from 'stream'

class ReadableString extends Readable {
    private sent = false

    constructor(
        private str: string
    ) {
        super();
    }

    _read() {
        if (!this.sent) {
            this.push(Buffer.from(this.str));
            this.sent = true
        }
        else {
            this.push(null)
        }
    }
}

const stringStream = new ReadableString('string to be streamed...')
Russell Briggs
fonte
1

O JavaScript é do tipo pato, portanto, se você apenas copiar a API de um fluxo legível , ele funcionará bem. De fato, você provavelmente não pode implementar a maioria desses métodos ou apenas deixá-los como stubs; tudo o que você precisa implementar é o que a biblioteca usa. Você pode usar a EventEmitterclasse pré-criada do Node para lidar com eventos também, para que você não precise implementar addListenerisso.

Veja como você pode implementá-lo no CoffeeScript:

class StringStream extends require('events').EventEmitter
  constructor: (@string) -> super()

  readable: true
  writable: false

  setEncoding: -> throw 'not implemented'
  pause: ->    # nothing to do
  resume: ->   # nothing to do
  destroy: ->  # nothing to do
  pipe: -> throw 'not implemented'

  send: ->
    @emit 'data', @string
    @emit 'end'

Então você pode usá-lo assim:

stream = new StringStream someString
doSomethingWith stream
stream.send()
icktoofay
fonte
Eu recebo o seguinte: TypeError: string is not a function at String.CALL_NON_FUNCTION (native) quando eu usá-lo comonew StringStream(str).send()
pathikrit 6/06
Só porque o JavaScript usa a digitação de pato não significa que você deve reinventar a roda. O nó já fornece uma implementação para fluxos. Basta criar uma nova instância stream.Readablecomo a sugerida por @Garth Kidd.
Sukima
4
@Sukima: stream.Readable não existia quando escrevi esta resposta.
Icktoofay