Como fechar um fluxo legível (antes do final)?

89

Como fechar um fluxo legível em Node.js?

var input = fs.createReadStream('lines.txt');

input.on('data', function(data) {
   // after closing the stream, this will not
   // be called again

   if (gotFirstLine) {
      // close this stream and continue the
      // instructions from this if
      console.log("Closed.");
   }
});

Isso seria melhor do que:

input.on('data', function(data) {
   if (isEnded) { return; }

   if (gotFirstLine) {
      isEnded = true;
      console.log("Closed.");
   }
});

Mas isso não iria parar o processo de leitura ...

Ionică Bizău
fonte
6
Aviso: esta questão é apenas no contexto do fsmódulo. closenão existe em Stream.Readable.
zamnuts
3
Boas notícias. Node versão 8 fornecestream.destroy()
joeytwiddle
você não pode ligarreadable.push(null) && readable.destroy();
Alexander Mills

Respostas:

39

Invoque input.close(). Não está nos documentos, mas

https://github.com/joyent/node/blob/cfcb1de130867197cbc9c6012b7e84e08e53d032/lib/fs.js#L1597-L1620

claramente faz o trabalho :) Na verdade, faz algo semelhante ao seu isEnded.

EDITAR 2015-abr-19 Com base nos comentários abaixo, e para esclarecer e atualizar:

  • Esta sugestão é um hack e não está documentada.
  • Embora, olhando para o atual, lib/fs.jsele ainda funcione> 1,5 anos depois.
  • Concordo com o comentário abaixo sobre ligar destroy()ser preferível.
  • Como afirmado corretamente abaixo, isso funciona para fs ReadStreams's, não em um genéricoReadable

Quanto a uma solução genérica: não parece que exista, pelo menos pelo meu entendimento da documentação e por uma rápida olhada _stream_readable.js.

Minha proposta seria colocar seu fluxo legível em modo pausado , pelo menos evitando processamento adicional em sua fonte de dados upstream. Não se esqueça de unpipe()remover todos os dataouvintes de eventos para que pause()realmente sejam pausados, conforme mencionado nos documentos

Nitzan Shaked
fonte
Faça mais: adicione algumas referências da documentação! :-)
Ionică Bizău
2
Muito bom! O código-fonte parece ser o melhor recurso de documentação. ;-)
Ionică Bizău
Na verdade, eu preferiria ligar em destroyvez disso. Pelo menos é o que é chamado se você definir autoClose como true. Olhando o código fonte (hoje) as diferenças são mínimas ( destroyligações close), mas isso pode mudar no futuro
Marcelo Diniz
@NitzanShaked O arquivo foi atualizado. Este é o link correto: github.com/joyent/node/blob/… ? (contém o ID do commit, então não será alterado no futuro)
Ionică Bizău
4
Não há nenhum close()objeto Legível, há uma solução nunca? Minha troca de dados está sempre incompleta ...
CodeManX
71

Edit: Boas notícias! A partir do Node.js 8.0.0 readable.destroyestá oficialmente disponível: https://nodejs.org/api/stream.html#stream_readable_destroy_error

ReadStream.destroy

Você pode chamar a função ReadStream.destroy a qualquer momento.

var fs = require('fs');

var readStream = fs.createReadStream('lines.txt');
readStream
    .on('data', function (chunk) {
        console.log(chunk);
        readStream.destroy();
    })
    .on('end', function () {
        // This may not been called since we are destroying the stream
        // the first time 'data' event is received
        console.log('All the data in the file has been read');
    })
    .on('close', function (err) {
        console.log('Stream has been destroyed and file has been closed');
    });

A função pública ReadStream.destroynão está documentada (Node.js v0.12.2), mas você pode dar uma olhada no código-fonte no GitHub ( commit de 5 de outubro de 2012 ).

A destroyfunção marca internamente a ReadStreaminstância como destruída e chama a closefunção para liberar o arquivo.

Você pode ouvir o evento close para saber exatamente quando o arquivo é fechado. O evento de término não será disparado a menos que os dados sejam totalmente consumidos.


Observe que as funções destroy(e close) são específicas para fs.ReadStream . Não faz parte da "interface" stream.readable genérica .

Yves M.
fonte
Pelo menos na última versão do Node (não verifiquei as outras), o descritor de arquivo é fechado automaticamente . Dito isso, não fiz nenhum tipo de teste completo para garantir que o fluxo eventualmente seja acionado errorse nunca for lido. Além disso, o único outro vazamento com o qual eu me preocuparia são os manipuladores de eventos - mais uma vez, não tenho 100% de certeza sobre isso, mas podemos estar bem, porque 2010 o evangelho de Isaacs diz que os manipuladores são podado quando os emissores são obtidos: groups.google.com/d/msg/nodejs/pXbJVo0NtaY/BxUmF_jp9LkJ
mikermcneil
1
Se os dados forem muito pequenos, o on('data') será disparado apenas uma vez, então não haverá nenhum .close(), basta lembrar outra pessoa.
bitfishxyz
12

Você não pode. Não há maneira documentada de fechar / desligar / abortar / destruir um fluxo legível genérico a partir do Nó 5.3.0. Esta é uma limitação da arquitetura de fluxo do Node.

Como outras respostas aqui explicaram, existem hacks não documentados para implementações específicas de Readable fornecidas pelo Node, como fs.ReadStream . No entanto, essas não são soluções genéricas para qualquer legível.

Se alguém puder provar que estou errado aqui, por favor, faça. Eu gostaria de poder fazer o que estou dizendo que é impossível, e ficaria muito feliz em ser corrigido.

EDIT : Aqui estava minha solução alternativa: implementar .destroy()para meu pipeline por meio de uma série complexa de unpipe()chamadas. E depois de toda essa complexidade, ele não funciona corretamente em todos os casos .

EDIT : Node v8.0.0 adicionado um destroy()api para streams legíveis .

thejoshwolfe
fonte
1
Existe agora stream.pipeline, que afirma lidar com “erros de encaminhamento e limpeza adequada e fornecer um retorno de chamada quando o pipeline estiver concluído”. Isso ajuda?
andrewdotn
11

Na versão, 4.*.*empurrar um valor nulo para o fluxo irá disparar um EOFsinal.

Dos documentos do nodejs

Se um valor diferente de nulo for passado, o método push () adiciona um pedaço de dados na fila para os processadores de fluxo subsequentes consumirem. Se null for passado, ele sinaliza o fim do fluxo (EOF), após o qual nenhum outro dado pode ser gravado.

Isso funcionou para mim depois de tentar várias outras opções nesta página.

Jacob Lowe
fonte
1
Funciona para mim. No entanto, eu precisava evitar chamar o retorno de chamada done () depois de enviar null para obter o comportamento esperado - ou seja, que todo o fluxo seja interrompido.
Rich Apodaca
6

Este módulo destroy tem como objetivo garantir que um stream seja destruído, tratando de diferentes APIs e bugs do Node.js. Agora é uma das melhores escolhas.

NB. No Nó 10, você pode usar o .destroymétodo sem dependências adicionais.

Rocco Musolino
fonte
3

Você pode limpar e fechar o fluxo com yourstream.resume(), o que irá despejar tudo no fluxo e, eventualmente, fechá-lo.

Dos documentos oficiais :

readable.resume ():

Retorno: este

Este método fará com que o fluxo legível retome a emissão de eventos de 'dados'.

Este método mudará o fluxo para o modo de fluxo. Se você não deseja consumir os dados de um fluxo, mas deseja chegar ao seu evento 'final', pode chamar stream.resume () para abrir o fluxo de dados.

var readable = getReadableStreamSomehow();
readable.resume();
readable.on('end', () => {
  console.log('got to the end, but did not read anything');
});
Sayem
fonte
Isso pode ser chamado de "drenagem" do fluxo. Em nosso caso, é claro que tínhamos um 'data'ouvinte de evento, mas fizemos com que ele marcasse um booleano if (!ignoring) { ... }para que ele não processasse dados quando estivermos drenando o fluxo. ignoring = true; readable.resume();
joeytwiddle
5
Claro, isso pressupõe que o fluxo 'end'em algum ponto. Nem todos os streams farão isso! (Por exemplo, um stream que envia a data a cada segundo, para sempre.)
joeytwiddle
3

É uma pergunta antiga, mas eu também estava procurando a resposta e encontrei a melhor para a minha implementação. Ambos os eventos ende closesão emitidos, então acho que esta é a solução mais limpa.

Isso resolverá o problema no nó 4.4. * (Versão estável no momento da escrita):

var input = fs.createReadStream('lines.txt');

input.on('data', function(data) {
   if (gotFirstLine) {
      this.end(); // Simple isn't it?
      console.log("Closed.");
   }
});

Para uma explicação muito detalhada, consulte: http://www.bennadel.com/blog/2692-you-have-to-explicitly-end-streams-after-pipes-break-in-node-js.htm

Vexter
fonte
2

Este código aqui vai fazer o truque muito bem:

function closeReadStream(stream) {
    if (!stream) return;
    if (stream.close) stream.close();
    else if (stream.destroy) stream.destroy();
}

writeStream.end () é a melhor maneira de fechar um writeStream ...

g00dnatur3
fonte
Por que você menciona .end () é o caminho certo, mas seu código usa close e destroy e nem mesmo end?
Lucas B
1
estou fechando um readStream no exemplo ... um writeStream - use.end
g00dnatur3