Skip to content

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:

ts
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.

ts
for await (const chunk of stream) {
  console.log(chunk);
}

// hello
// world

ReadableStream 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.

ts
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.

ts
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.

ts
const response = new Response(
  (async function* () {
    yield "hello";
    yield "world";
  })(),
);

await response.text(); // "helloworld"

Você também pode usar [Symbol.asyncIterator] diretamente.

ts
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.

ts
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.

ts
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.

ts
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.

ts
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.

ts
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:

ts
const sink = new Bun.ArrayBufferSink();
sink.start({
  highWaterMark: 1024 * 1024, // 1 MB
});

Referência

ts
/**
 * 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>;
}

Bun by www.bunjs.com.cn edit