¿Cómo crear secuencias desde una cadena en Node.Js?

Respuestas:

27

Desde el nodo 10.17, stream.Readable tiene un frommétodo para crear fácilmente streams desde cualquier iterable (que incluye literales 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"`
})

Tenga en cuenta que al menos entre 10.17 y 12.3, una cadena es en sí misma iterable, por Readable.from("input string")lo que funcionará, pero emite un evento por carácter.Readable.from(["input string"])emitirá un evento por elemento en la matriz (en este caso, un elemento).

También tenga en cuenta que en los nodos posteriores (probablemente 12.3, ya que la documentación dice que la función se cambió entonces), ya no es necesario envolver la cadena en una matriz.

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

Fizker
fuente
De acuerdo con stream.Readable.from , Calling Readable.from (string) o Readable.from (buffer) no hará que las cadenas o buffers se repitan para que coincidan con la semántica de los otros streams por razones de rendimiento.
abbr
Culpa mía. La función se agregó en 10.7 y se comportó de la manera que describí originalmente. En algún momento desde entonces, las cadenas ya no necesitan estar envueltas en matrices (desde 12.3, ya no itera cada carácter individualmente).
Fizker
186

Como @substack me corrigió en #node , la nueva API de secuencias en el Nodo v10 lo hace más fácil:

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

... después de lo cual puede canalizarlo libremente o, de lo contrario, pasarlo a su consumidor previsto.

No es tan limpio como el resumen de una línea, pero evita la dependencia adicional.

( Actualización: desde la v0.10.26 hasta la v9.2.1 hasta ahora, una llamada pushdirecta desde el indicador REPL se bloqueará con una not implementedexcepción si no la configuró _read. No se bloqueará dentro de una función o un script. Si la inconsistencia lo hace nervioso, incluye el noop.)

Garth Kidd
fuente
66
Desde los documentos (enlace) : "Todas las implementaciones de flujo legible deben proporcionar un _readmétodo para obtener datos del recurso subyacente".
Felix Rabe
2
@eye_mew necesita primero ('stream')
Jim Jones
8
¿Por qué empujas nullen el búfer de la secuencia?
dopatraman
55
@dopatraman nullle dice a la transmisión que ha terminado de leer todos los datos y cerrar la transmisión
chrishiestand
2
Parece que no deberías hacerlo de esta manera. Citando los documentos : "El readable.push()método debe ser llamado solo por implementadores legibles, y solo desde dentro del readable._read()método".
Axel Rauschmayer
127

No use la respuesta del curriculum vitae de Jo Liss. Funcionará en la mayoría de los casos, pero en mi caso me perdió una buena búsqueda de errores de 4 o 5 horas. No hay necesidad de módulos de terceros para hacer esto.

NUEVA RESPUESTA :

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

Esto debería ser una transmisión legible totalmente compatible. Consulte aquí para obtener más información sobre cómo usar las transmisiones correctamente.

ANTIGUA RESPUESTA : Solo use la secuencia PassThrough nativa:

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

Tenga en cuenta que el evento 'close' no se emite (lo cual no es requerido por las interfaces de flujo).

BT
fuente
2
@Finn No necesita los parens en javascript si no hay ningún argumento
BT
¡no uses "var" en 2018! but const
stackdave
30

Simplemente cree una nueva instancia del streammódulo y personalícela según sus necesidades:

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

o

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
fuente
13
Este código rompe las convenciones de flujo. pipe()se supone que devuelve la secuencia de destino, al menos.
greim
2
El evento final no se llama si usa este código. Esta no es una buena manera de crear una secuencia que se pueda usar en general.
BT
12

Editar: la respuesta de Garth es probablemente mejor.

Mi antiguo texto de respuesta se conserva a continuación.


Para convertir una cadena en una corriente, se puede utilizar una pausa a través de la secuencia:

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

Ejemplo:

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
fuente
No pude conseguir que la solución de zeMirco funcionara para mi caso de uso, pero resumerfuncionó bastante bien. ¡Gracias!
mpen
La sugerencia de reanudar @substack funcionó muy bien para mí. ¡Gracias!
Garth Kidd
2
El resumen es excelente, pero el "reanuda automáticamente la transmisión en nextTick" puede generar sorpresas si espera que pueda transmitir la transmisión a consumidores desconocidos. Tenía un código que canalizaba una secuencia de contenido a un archivo si un guardado de metadatos con base de datos tenía éxito. Ese fue un error al acecho, sucedió con éxito cuando db write devolvió el éxito de inmediato. Más tarde refactoré las cosas para que estuvieran dentro de un bloque asíncrono, y boom, la transmisión nunca fue legible. Lección: si no sabe quién va a consumir su transmisión, siga la técnica through (). Pause (). Queue ('string'). End ().
Jolly Roger
1
Pasé alrededor de 5 horas depurando mi código porque utilicé la parte del resumen de esta respuesta. Sería genial si pudiera ... eliminarlo
BT
10

Hay un módulo para eso: https://www.npmjs.com/package/string-to-stream

var str = require('string-to-stream')
str('hi there').pipe(process.stdout) // => 'hi there' 
Lori
fuente
1
¿Es este un juego de palabras con "hay una aplicación para eso"? ;)
masterxilo
1
El enlace en el comentario es útil: npmjs.com/package/string-to-stream
Dem Pilafian
Para su información, intenté usar esta biblioteca para escribir JSON en Google Drive, pero no me funcionó. Escribió un artículo sobre esto aquí: medium.com/@dupski/… . También se ha añadido como una continuación respuesta
Russell Briggs
6

en cafe-script:

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

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

úsalo:

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

Otra solución es pasar la función de lectura al constructor de Readable (cf opciones de lectura de flujo de documentos )

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

puede después de usar en la tubería por ejemplo

Philippe T.
fuente
¿Cuál es el propósito de la devolución al final?
Kirill Reznikov
"siempre devuelve algo (o nada)", este ejemplo de la documentación.
Philippe
En JS, si una función no tiene un retorno, es equivalente a su retorno vacío. ¿Podría proporcionar un enlace donde lo haya encontrado?
Kirill Reznikov
deberías tener razón. Lo dije más para las mejores prácticas. No quiero devolver nada, no es un error. Entonces quito la línea.
Philippe T.
5

Me cansé de tener que volver a aprender esto cada seis meses, así que publiqué un módulo npm para abstraer los detalles de implementación:

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

Este es el núcleo del 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;

stres lo stringque se debe pasar al constructor al invocarlo, y el flujo lo generará como datos. optionsson las opciones típicas que se pueden pasar a una secuencia, según la documentación .

Según Travis CI, debería ser compatible con la mayoría de las versiones de nodo.

Chris Allen Lane
fuente
2
Cuando publiqué esto inicialmente, no incluí el código relevante, que me dijeron que estaba mal visto.
Chris Allen Lane,
2

Heres una solución ordenada en 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
fuente
1

JavaScript es de tipo pato, por lo que si solo copia una API legible , funcionará bien. De hecho, probablemente no pueda implementar la mayoría de esos métodos o simplemente dejarlos como apéndices; todo lo que necesitará implementar es lo que usa la biblioteca. Puedes usar el preconstruido de NodeEventEmitter clase para lidiar con eventos, por lo que no tiene que implementaraddListener usted mismo.

A continuación, se explica cómo implementarlo en 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'

Entonces podrías usarlo así:

stream = new StringStream someString
doSomethingWith stream
stream.send()
icktoofay
fuente
Me sale esto: TypeError: string is not a function at String.CALL_NON_FUNCTION (native) cuando lo uso comonew StringStream(str).send()
pathikrit
El hecho de que JavaScript use la escritura de pato no significa que deba reinventar la rueda. El nodo ya proporciona una implementación para las secuencias. Simplemente cree una nueva instancia stream.Readablecomo la sugerida por @Garth Kidd.
Sukima
44
@Sukima: stream.Readable no existía cuando escribí esta respuesta.
icktoofay