Comment créer des flux à partir d'une chaîne dans Node.Js?

177

J'utilise une bibliothèque, ya-csv , qui attend un fichier ou un flux en entrée, mais j'ai une chaîne.

Comment convertir cette chaîne en un flux dans Node?

pathikrit
la source

Réponses:

27

À partir du nœud 10.17, stream.Readable a une fromméthode pour créer facilement des flux à partir de n'importe quel itérable (qui comprend des littéraux de tableau):

const { Readable } = require("stream")

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

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

Notez qu'au moins entre 10.17 et 12.3, une chaîne est elle-même un itérable, donc Readable.from("input string")fonctionnera, mais émettra un événement par caractère. Readable.from(["input string"])émettra un événement par élément du tableau (dans ce cas, un élément).

Notez également que dans les nœuds ultérieurs (probablement 12.3, puisque la documentation dit que la fonction a été modifiée à ce moment-là), il n'est plus nécessaire d'envelopper la chaîne dans un tableau.

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

Fizker
la source
Selon stream.Readable.from , appeler Readable.from (string) ou Readable.from (buffer) n'aura pas les chaînes ou les tampons être itérés pour correspondre à la sémantique des autres flux pour des raisons de performances.
abbr
Ma faute. La fonction a été ajoutée dans 10.7 et s'est comportée comme je l'ai décrite à l'origine. Parfois, les chaînes n'ont plus besoin d'être enveloppées dans des tableaux (depuis 12.3, il n'itère plus chaque caractère individuellement).
Fizker
186

Comme @substack m'a corrigé dans #node , la nouvelle API de flux dans Node v10 facilite les choses:

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

… Après quoi vous pouvez le canaliser librement ou le transmettre d'une autre manière à votre consommateur.

Ce n'est pas aussi propre que le one-liner de reprise , mais cela évite la dépendance supplémentaire.

( Mise à jour: de v0.10.26 à v9.2.1 jusqu'à présent, un appel à pushdirectement depuis l'invite REPL plantera avec une not implementedexception si vous ne l'avez pas défini _read. Il ne plantera pas à l'intérieur d'une fonction ou d'un script. Si une incohérence vous rend nerveux, incluez le noop.)

Garth Kidd
la source
6
À partir de la documentation (lien) : "Toutes les implémentations de flux lisibles doivent fournir une _readméthode pour récupérer les données de la ressource sous-jacente."
Felix Rabe
2
@eye_mew vous devez d'abord exiger ('stream')
Jim Jones
8
Pourquoi poussez-vous nulldans la mémoire tampon du flux?
dopatraman
5
@dopatraman nulldit au flux qu'il a fini de lire toutes les données et de fermer le flux
chrishiestand
2
Il semble que vous ne devriez pas le faire de cette façon. Citant la documentation : «La readable.push()méthode est destinée à être appelée uniquement par les implémenteurs en lecture et uniquement à partir de la readable._read()méthode.»
Axel Rauschmayer
127

N'utilisez pas la réponse de reprise de Jo Liss. Cela fonctionnera dans la plupart des cas, mais dans mon cas, cela m'a fait perdre 4 ou 5 heures de recherche de bogues. Il n'y a pas besoin de modules tiers pour ce faire.

NOUVELLE RÉPONSE :

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

Il doit s'agir d'un flux lisible entièrement conforme. Voir ici pour plus d'informations sur la façon d'utiliser correctement les flux.

ANSWER : Utilisez simplement le flux PassThrough natif:

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

Notez que l'événement 'close' n'est pas émis (ce qui n'est pas requis par les interfaces de flux).

BT
la source
2
@Finn Vous n'avez pas besoin des parens en javascript s'il n'y a pas d'arguments
BT
n'utilisez pas "var" en 2018! but const
stackdave
30

Créez simplement une nouvelle instance du streammodule et personnalisez-la en fonction de vos besoins:

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
la source
13
Ce code rompt les conventions de flux. pipe()est censé renvoyer au moins le flux de destination.
greim
2
L'événement de fin n'est pas appelé si vous utilisez ce code. Ce n'est pas un bon moyen de créer un flux qui peut être utilisé de manière générale.
BT
12

Edit: La réponse de Garth est probablement meilleure.

Mon ancien texte de réponse est conservé ci-dessous.


Pour convertir une chaîne à un flux, vous pouvez utiliser un pause par flux:

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

Exemple:

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
la source
Je n'ai pas pu faire fonctionner la solution de zeMirco pour mon cas d'utilisation, mais cela resumera plutôt bien fonctionné. Merci!
mpen
La suggestion de revendeur @substack a très bien fonctionné pour moi. Merci!
Garth Kidd
2
Resumer est génial, mais la fonction "reprend automatiquement le flux sur nextTick" peut donner des surprises si vous pensez pouvoir transmettre le flux à des consommateurs inconnus! J'avais du code qui dirigeait un flux de contenu vers un fichier si une sauvegarde de la base de données des métadonnées réussissait. C'était un bug qui se cachait, il est arrivé à réussir lorsque l'écriture de la base de données a immédiatement renvoyé le succès! Plus tard, j'ai remanié les choses pour qu'elles soient à l'intérieur d'un bloc asynchrone, et boum, le flux n'était jamais lisible. Leçon: si vous ne savez pas qui va consommer votre flux, respectez la technique through (). Pause (). Queue ('string'). End ().
Jolly Roger
1
J'ai passé environ 5 heures à déboguer mon code parce que j'ai utilisé la partie resumer de cette réponse. Ce serait génial si vous pouviez aimer ... supprimer
BT
10

Il existe un module pour cela: https://www.npmjs.com/package/string-to-stream

var str = require('string-to-stream')
str('hi there').pipe(process.stdout) // => 'hi there' 
Lori
la source
1
Est-ce un jeu de mots sur "il y a une application pour ça"? ;)
masterxilo
1
Le lien dans le commentaire est l'utile: npmjs.com/package/string-to-stream
Dem Pilafian
Pour info, j'ai essayé d'utiliser cette bibliothèque pour écrire du JSON sur Google Drive, mais cela ne fonctionnerait pas pour moi. A écrit un article à ce sujet ici: medium.com/@dupski/… . Également ajouté comme réponse ci
Russell Briggs
6

dans coffee-script:

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

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

utilise le:

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

Une autre solution est de passer la fonction de lecture au constructeur de Readable (cf doc stream readable options )

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

vous pouvez après utilisation sur le tuyau par exemple

Philippe T.
la source
quel est le but du retour à la fin?
Kirill Reznikov
"toujours retourner quelque chose (ou rien)", cet exemple de la documentation.
Philippe T.
Dans JS, si une fonction n'a pas de retour, c'est un équivalent à votre retour vide. Pourriez-vous s'il vous plaît fournir un lien où vous l'avez trouvé?
Kirill Reznikov
vous devriez avoir raison. Je l'ai dit plus pour les meilleures pratiques. Je ne veux rien rendre, ce n'est pas une erreur. Alors je supprime la ligne.
Philippe T.
5

J'étais fatigué de devoir réapprendre cela tous les six mois, alors je viens de publier un module npm pour résumer les détails de l'implémentation:

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

C'est le cœur du module:

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;

strest le stringqui doit être passé au constructeur lors de l'invocation, et sera produit par le flux en tant que données. optionssont les options typiques qui peuvent être passées à un flux, selon la documentation .

Selon Travis CI, il devrait être compatible avec la plupart des versions de node.

Chris Allen Lane
la source
2
Lorsque j'ai publié ceci au départ, je n'ai pas inclus le code pertinent, ce qui m'a dit qu'il était mal vu.
Chris Allen Lane
2

Voici une solution ordonnée dans 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
la source
1

JavaScript est de type canard, donc si vous copiez simplement l'API d'un flux lisible , cela fonctionnera très bien. En fait, vous ne pouvez probablement pas implémenter la plupart de ces méthodes ou simplement les laisser sous forme de stubs; tout ce dont vous aurez besoin pour implémenter est ce que la bibliothèque utilise. Vous pouvez également utiliser la EventEmitterclasse pré-construite de Node pour gérer les événements, de sorte que vous n'ayez pas à implémenter addListenervous-même.

Voici comment vous pouvez l'implémenter dans 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'

Ensuite, vous pouvez l'utiliser comme ceci:

stream = new StringStream someString
doSomethingWith stream
stream.send()
icktoofay
la source
Je comprends ceci: TypeError: string is not a function at String.CALL_NON_FUNCTION (native) quand je l'utilise commenew StringStream(str).send()
pathikrit
Ce n'est pas parce que JavaScript utilise le typage canard que vous devez réinventer la roue. Node fournit déjà une implémentation pour les flux. Créez simplement une nouvelle instance du stream.Readabletype de @Garth Kidd suggéré.
Sukima
4
@Sukima: stream.Readable n'existait pas lorsque j'ai écrit cette réponse.
icktoofay