Come creare flussi da stringa in Node.Js?


Risposte:


27

Dal nodo 10.17, stream.Readable ha un frommetodo per creare facilmente flussi da qualsiasi iterabile (che include valori letterali di array):

const { Readable } = require("stream")

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

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

Nota che almeno tra 10.17 e 12.3, una stringa è essa stessa un iterabile, quindi Readable.from("input string")funzionerà, ma emetterà un evento per carattere. Readable.from(["input string"])emetterà un evento per elemento nell'array (in questo caso, un elemento).

Si noti inoltre che nei nodi successivi (probabilmente 12.3, poiché la documentazione indica che la funzione è stata modificata in quel momento), non è più necessario avvolgere la stringa in un array.

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


Secondo stream.Readable.from , Calling Readable.from (stringa) o Readable.from (buffer) non avrà iterazioni di stringhe o buffer per abbinare la semantica di altri flussi per motivi di prestazioni.
abbr

Colpa mia. La funzione è stata aggiunta in 10.7 e si è comportata come descritto in origine. Da allora, le stringhe non hanno più bisogno di essere avvolte in array (dal 12.3, non ripetono più ogni singolo personaggio).
Fizker,

186

Poiché @substack mi ha corretto in #node , la nuova API stream in Node v10 rende tutto più semplice:

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

... dopo di che puoi liberamente pipe o in altro modo passarlo al consumatore previsto.

Non è pulito come il resumer one-liner, ma evita la dipendenza aggiuntiva.

( Aggiornamento: dalla v0.10.26 alla v9.2.1 finora, una chiamata a pushdirettamente dal prompt REPL si arresta in modo anomalo con not implementedun'eccezione se non è stata impostata _read. Non si arresta in modo anomalo all'interno di una funzione o di uno script. Se l'incoerenza ti rende nervoso, includi il noop.)


6
Dai documenti (link) : "Tutte le implementazioni del flusso leggibile devono fornire un _readmetodo per recuperare i dati dalla risorsa sottostante."
Felix Rabe,

2
@eye_mew devi prima richiedere ('stream')
Jim Jones il

8
Perché spingi nullnel buffer del flusso?
dopatraman,

5
@dopatraman nulldice allo stream che ha finito di leggere tutti i dati e di chiudere lo stream
chrishiestand

2
Sembra che non dovresti farlo in questo modo. Citando i documenti : "Il readable.push()metodo è destinato a essere chiamato solo dagli Implementatori leggibili e solo dall'interno del readable._read()metodo."
Axel Rauschmayer,

127

Non usare la risposta del resumer di Jo Liss. Funzionerà nella maggior parte dei casi, ma nel mio caso mi ha perso una buona ricerca di bug di 4 o 5 ore. Non è necessario che moduli di terze parti facciano questo.

NUOVA RISPOSTA :

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

Questo dovrebbe essere un flusso leggibile pienamente conforme. Vedi qui per maggiori informazioni su come usare gli stream correttamente.

RISPOSTA VECCHIA : basta usare il flusso 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
})

Si noti che l'evento 'close' non viene emesso (che non è richiesto dalle interfacce dello stream).


2
@Finn Non hai bisogno dei genitori in javascript se non ci sono discussioni
BT

non usare "var" nel 2018! ma const
stackdave il

30

Basta creare una nuova istanza del streammodulo e personalizzarla in base alle proprie esigenze:

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

13
Questo codice rompe le convenzioni sul flusso. pipe()dovrebbe restituire il flusso di destinazione, almeno.
greim,

2
L'evento finale non viene chiamato se si utilizza questo codice. Questo non è un buon modo per creare un flusso che può essere utilizzato in generale.
BT,

12

Modifica: la risposta di Garth è probabilmente migliore.

Il mio vecchio testo di risposta è conservato di seguito.


Per convertire una stringa in un ruscello, è possibile utilizzare un pausa attraverso il flusso:

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

Esempio:

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

Non riuscivo a far funzionare la soluzione di zeMirco per il mio caso d'uso, ma resumerfunzionava abbastanza bene. Grazie!
mpen

Il suggerimento del resumer @substack ha funzionato molto bene per me. Grazie!
Garth Kidd,

2
Resumer è eccezionale, ma il "ripristino automatico dello stream su nextTick" può generare sorprese se ti aspetti di poter passare lo stream a consumatori sconosciuti! Ho avuto un po 'di codice che ha convogliato un flusso di contenuto in un file se un salvataggio db di metadati è riuscito. Era un bug in agguato, è successo quando il db write ha restituito immediatamente il successo! In seguito ho riformulato le cose in modo che fossero all'interno di un blocco asincrono e, boom, il flusso non era mai leggibile. Lezione: se non sai chi consumerà il tuo stream, segui la tecnica through (). Pause (). Queue ('string'). End ().
Jolly Roger,

1
Ho impiegato circa 5 ore a eseguire il debug del mio codice perché ho usato la parte resumer di questa risposta. Sarebbe bello se potessi ... rimuoverlo
BT

10

C'è un modulo per questo: https://www.npmjs.com/package/string-to-stream

var str = require('string-to-stream')
str('hi there').pipe(process.stdout) // => 'hi there' 

1
È un gioco di parole "c'è un'app per quello"? ;)
masterxilo

1
Il link nel commento è quello utile: npmjs.com/package/string-to-stream
Dem Pilafian

Cordiali saluti, ho provato a utilizzare questa libreria per scrivere JSON su Google Drive, ma non avrebbe funzionato per me. Ho scritto un articolo su questo qui: medium.com/@dupski/… . Aggiunto anche come risposta di seguito
Russell Briggs,

6

nella sceneggiatura del caffè:

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

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

usalo:

new StringStream('text here').pipe(stream1).pipe(stream2)

6

Un'altra soluzione sta passando la funzione di lettura al costruttore di Readable (vedi opzioni leggibili del flusso di documenti )

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

dopo l'uso puoi usare s.pipe per esempio


qual è lo scopo del ritorno alla fine?
Kirill Reznikov il

"restituisci sempre qualcosa (o niente)", questo esempio dalla documentazione.
Philippe T.

In JS, se una funzione non ha un ritorno è equivalente al tuo ritorno vuoto. Potresti fornire un link dove l'hai trovato?
Kirill Reznikov il

dovresti aver ragione. L'ho detto di più per le migliori pratiche. Non voglio restituire nulla, non è un errore. Quindi rimuovo la linea.
Philippe T.

5

Mi sono stancato di doverlo imparare di nuovo ogni sei mesi, quindi ho appena pubblicato un modulo npm per sottrarre i dettagli dell'implementazione:

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

Questo è il nucleo del modulo:

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è quello stringche deve essere passato al costruttore al momento dell'invocazione e verrà emesso dal flusso come dati. optionssono le opzioni tipiche che possono essere passate a un flusso, secondo la documentazione .

Secondo Travis CI, dovrebbe essere compatibile con la maggior parte delle versioni del nodo.


2
Quando l'ho pubblicato inizialmente, non ho incluso il codice pertinente, che mi è stato detto che è disapprovato.
Chris Allen Lane,

2

Ecco una soluzione ordinata in 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...')

1

JavaScript è di tipo anatra, quindi se copi semplicemente l'API di un flusso leggibile , funzionerà perfettamente. In effetti, probabilmente non puoi implementare la maggior parte di questi metodi o semplicemente lasciarli come stub; tutto ciò che devi implementare è ciò che utilizza la libreria. Puoi anche usare la EventEmitterclasse pre-costruita di Node per gestire gli eventi, quindi non devi implementare addListenerte stesso.

Ecco come è possibile implementarlo in 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'

Quindi potresti usarlo in questo modo:

stream = new StringStream someString
doSomethingWith stream
stream.send()

Capisco questo: TypeError: string is not a function at String.CALL_NON_FUNCTION (native) quando lo uso in questo modonew StringStream(str).send()
pathikrit

Solo perché JavaScript utilizza la digitazione anatra non significa che dovresti reinventare la ruota. Il nodo fornisce già un'implementazione per gli stream. Basta creare una nuova istanza stream.Readablecome suggerito da @Garth Kidd.
Sukima,

4
@Sukima: stream.Readable non esisteva quando ho scritto questa risposta.
icktoofay,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.