Streams são uma abstração importante para trabalhar com dados binários sem carregá-los todos na memória de uma vez. Eles são comumente usados para ler e escrever arquivos, enviar e receber requisições de rede, e processar grandes quantidades de dados.
O Bun implementa as Web APIs ReadableStream e WritableStream.
NOTE
O Bun também implementa o módulo `node:stream`, incluindo [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), e [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). Para documentação completa, consulte a [documentação do Node.js](https://nodejs.org/api/stream.html).Para criar um ReadableStream simples:
const stream = new ReadableStream({
start(controller) {
controller.enqueue("hello");
controller.enqueue("world");
controller.close();
},
});O conteúdo de um ReadableStream pode ser lido chunk-by-chunk com a sintaxe for await.
for await (const chunk of stream) {
console.log(chunk);
}
// hello
// worldReadableStream Direto
O Bun implementa uma versão otimizada de ReadableStream que evita cópia de dados desnecessária e lógica de gerenciamento de fila.
Com um ReadableStream tradicional, chunks de dados são enqueued. Cada chunk é copiado para uma fila, onde fica até que o stream esteja pronto para enviar mais dados.
const stream = new ReadableStream({
start(controller) {
controller.enqueue("hello");
controller.enqueue("world");
controller.close();
},
});Com um ReadableStream direto, chunks de dados são escritos diretamente no stream. Nenhuma fila acontece, e não há necessidade de clonar os dados do chunk na memória. A API controller é atualizada para refletir isso; em vez de .enqueue() você chama .write.
const stream = new ReadableStream({
type: "direct",
pull(controller) {
controller.write("hello");
controller.write("world");
},
});Ao usar um ReadableStream direto, todo o enfileiramento de chunks é manipulado pelo destino. O consumidor do stream recebe exatamente o que é passado para controller.write(), sem qualquer encoding ou modificação.
Streams de gerador async
O Bun também suporta funções geradoras async como uma fonte para Response e Request. Esta é uma maneira fácil de criar um ReadableStream que busca dados de uma fonte assíncrona.
const response = new Response(
(async function* () {
yield "hello";
yield "world";
})(),
);
await response.text(); // "helloworld"Você também pode usar [Symbol.asyncIterator] diretamente.
const response = new Response({
[Symbol.asyncIterator]: async function* () {
yield "hello";
yield "world";
},
});
await response.text(); // "helloworld"Se você precisar de controle mais granular sobre o stream, yield retornará o controller ReadableStream direto.
const response = new Response({
[Symbol.asyncIterator]: async function* () {
const controller = yield "hello";
await controller.end();
},
});
await response.text(); // "hello"Bun.ArrayBufferSink
A classe Bun.ArrayBufferSink é um escritor incremental rápido para construir um ArrayBuffer de tamanho desconhecido.
const sink = new Bun.ArrayBufferSink();
sink.write("h");
sink.write("e");
sink.write("l");
sink.write("l");
sink.write("o");
sink.end();
// ArrayBuffer(5) [ 104, 101, 108, 108, 111 ]Para recuperar os dados como Uint8Array, passe a opção asUint8Array para o método start.
const sink = new Bun.ArrayBufferSink();
sink.start({
asUint8Array: true,
});
sink.write("h");
sink.write("e");
sink.write("l");
sink.write("l");
sink.write("o");
sink.end();
// Uint8Array(5) [ 104, 101, 108, 108, 111 ]O método .write() suporta strings, typed arrays, ArrayBuffer, e SharedArrayBuffer.
sink.write("h");
sink.write(new Uint8Array([101, 108]));
sink.write(Buffer.from("lo").buffer);
sink.end();Uma vez que .end() é chamado, nenhum dado adicional pode ser escrito no ArrayBufferSink. No entanto, no contexto de bufferizar um stream, é útil continuamente escrever dados e periodicamente .flush() o conteúdo (digamos, em um WriteableStream). Para suportar isso, passe stream: true para o construtor.
const sink = new Bun.ArrayBufferSink();
sink.start({
stream: true,
});
sink.write("h");
sink.write("e");
sink.write("l");
sink.flush();
// ArrayBuffer(5) [ 104, 101, 108 ]
sink.write("l");
sink.write("o");
sink.flush();
// ArrayBuffer(5) [ 108, 111 ]O método .flush() retorna os dados bufferizados como um ArrayBuffer (ou Uint8Array se asUint8Array: true) e limpa o buffer interno.
Para definir manualmente o tamanho do buffer interno em bytes, passe um valor para highWaterMark:
const sink = new Bun.ArrayBufferSink();
sink.start({
highWaterMark: 1024 * 1024, // 1 MB
});Referência
/**
* Escritor incremental rápido que se torna um `ArrayBuffer` no end().
*/
export class ArrayBufferSink {
constructor();
start(options?: {
asUint8Array?: boolean;
/**
* Pré-alocar um buffer interno deste tamanho
* Isso pode melhorar significativamente a performance quando o tamanho do chunk é pequeno
*/
highWaterMark?: number;
/**
* Em {@link ArrayBufferSink.flush}, retorna os dados escritos como um `Uint8Array`.
* Escritas reiniciarão do início do buffer.
*/
stream?: boolean;
}): void;
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
/**
* Flush do buffer interno
*
* Se {@link ArrayBufferSink.start} foi passado uma opção `stream`, isto retornará um `ArrayBuffer`
* Se {@link ArrayBufferSink.start} foi passado uma opção `stream` e `asUint8Array`, isto retornará um `Uint8Array`
* Caso contrário, isto retornará o número de bytes escritos desde o último flush
*
* Esta API pode mudar depois para separar Uint8ArraySink e ArrayBufferSink
*/
flush(): number | Uint8Array<ArrayBuffer> | ArrayBuffer;
end(): ArrayBuffer | Uint8Array<ArrayBuffer>;
}