Conjunção De Continuação - Classe de palavras conjunção tabelas resumo – Artofit
Classe de palavras conjunção tabelas resumo – Artofit

O operador yield\* e a delegação entre geradores

Se você já tentou encadear generadores em JavaScript sem usar yield*, provavelmente passou por aquela situação em que o resultado vira um array de arrays aninhados e você perde meia hora debugando. O yield* existe exatamente para resolver isso, mas o pessoal costuma tratá-lo como um detalhe avançado quando, na prática, é uma ferramenta do dia a dia.

o que é conjunção de continuação no contexto de generadores

A conjunção de continuação, no caso do JavaScript, se materializa no operador yield*. Ele delega a execução do gerador pai para outro gerador, permitindo que todos os valores produzidos pela subsequente sejam emitidos pelo gerador que chamou. Sem ele, você precisa fazer loops manuais, chamar .next() em repetição e acumulador na mão, o que quebra a legibilidade e adiciona linhas desnecessárias. Um gerador comum simplesmente emite valores com yield. Quando você coloca yield* antes de outro gerador, o mecanismo interno do JS entra no segundo gerador e repete o processo até que ele seja esgotado, retornando ao ponto original depois. Isso é tudo. Não tem mágica, só protocolo de iteração.

como implementar na prática

Veja um exemplo direto de como funciona: function* geradorInterno() {
    yield 1;
    yield 2;
    yield 3;
    }

function* geradorExterno() {
    yield 'inicio';
    yield* geradorInterno();
    yield 'fim';
    }

const resultado = [...geradorExterno()];
// ['inicio', 1, 2, 3, 'fim']

O yield* garante que os três valores internos apareçam na sequência correta dentro do resultado final. Se você trocar yield* por yield simples no geradorExterno, o que vai aparecer é um objeto gerador, não os valores individuais. Isso causa erros silenciosos em operações como Array.from() ou spread, porque o consumidor espera iteráveis, não um gerador em si.

caso real que eu enfrentei e como resolvi

Eu estava construindo um pipeline de ingestão de dados onde cada etapa era um gerador separado: leitura de CSV, limpeza, transformação e escrita. A lógica parecia certa, mas o fluxo de saída ficava com objetos gerador aninhados em vez dos valores processed. Eu tinha escrito yield no lugar de yield* na camada de orquestração. O dado passava intacto, mas quem consumia o resultado final via um iterável dentro de outro iterável, e o consumer esperava flat. A solução foi substituir todas as ocorrências de yield chamando diretamente o gerador intermediário por yield* geradorIntermediario(). Com isso, o pipeline se achata automaticamente e a saída sai na forma esperada. Não precisei alterar a assinatura das funções nem adicionar filtros extras. O tempo de desenvolvimento caiu de uma tarde inteira para uns vinte minutos só corrigindo isso.

👉 Clique no botão abaixo para saber mais sobre o assunto!

nuances que ninguém conta

Primeiro ponto: yield* pode receber qualquer iterável, não apenas geradores. Um array, uma string, um Set, tudo funciona. O operador chama Symbol.iterator internamente e itera. Se você passar algo que não é iterável, o JS lança TypeError imediatamente. Isso acontece com frequência quando se passa uma Promessa esperando que yield* resolva automaticamente, o que não acontece. Segundo ponto: o valor de retorno do gerador delegado pode ser capturado. Se o gerador interno retorna algo no final, yield* resolve para esse valor de retorno. Eu costumava usar esse comportamento para passar códigos de status ou metadados de uma etapa para outra sem criar variáveis globais no closure.

Terceiro ponto, e aqui está a pegadinha que quase sempre pega gente experiente também: se o gerador interno for suspenso por um throw ou return externo, o yield* propaga a interrupção para dentro do gerador delegado. Isso significa que finally blocks dentro do gerador interno realmente rodarão quando o externo for encerrado. É útil para limpeza de recursos, mas pode causar efeitos colaterais indesejados se você não estiver ciente disso.

quando não usar yield*

Existem cenários em que yield* piora o código. Se você precisa manipular cada valor antes de repassá-lo, aplicar um filtro ou transformar dados de um sub-iterável, usar yield* cegamente vai te obrigar a voltar e refatorar. Nesse caso, um for...of manual dentro do gerador pai oferece mais controle e ainda mantém a legibilidade. A diferença é pequena, mas relevante em pipelines complexos onde a transformação é parte da regra de negócio. Outro caso onde yield* não ajuda é quando o iterável delegado é enorme e você quer backpressure controlado. O yield* consome o iterável inteiro de uma vez no contexto de chamada, sem oportunidade de pausar entre blocos. Se o volume for grande, prefira iteração manual com verificação de tamanho.

erros comuns e como evitar

O erro mais frequente é confundir yield com yield* e gerar aninhamento indesejado. O sintoma é sempre o mesmo: o consumidor recebe um gerador onde esperava valores. A correção é verificar se o lado direito do operador é efetivamente um iterável e não um valor simples. Outro erro comum é esquecer que yield* passa a mensagem de envio (o valor enviado com .next()) para o gerador delegado. Se você precisa interceptar esse valor no gerador externo, o comportamento vai diferente do que se usa yield normal. Teste sempre com um .next('valor') e confirme o que chega em cada camada.

Há também o problema de chain encadeado demais. Cada yield* adiciona uma camada de abstração e, se você tiver cinco geradores encadeados um dentro do outro, o debug fica pesado. O ideal é manter a profundidade em no máximo dois níveis. Se precisar de mais, considere usar uma função de montagem que retorne um único gerador plano em vez de empilhar delegações.

resumo rápido

O yield* é a forma nativa do JavaScript de fazer conjunção de continuação entre geradores. Ele delega iteração, achata a saída, captura retornos finais e propaga interrupções. Funciona com qualquer iterável. Não funciona com promessas. Evite em cenários que precisam de transformação ou backpressure granular. E nunca esqueça de testar o que o consumidor final realmente recebe antes de assumir que o encadeamento está correto.