Um dos maiores desafios ao escrever código de front-end ou código Node.js é lidar com a assincronicidade. Houve uma revolução do gerador original quando pacotes como co nos permitiram escrever código assíncrono de aparência síncrona com construções normais como try e catch :

 co.wrap (function * () { tentar { yield fetch ('http://some.domain'); } catch (errar) { //lidar com }
}); 

A idade das trevas (assíncrono/espera)

Por volta dessa época, C # e.net começaram a enviar a construção async... await original que o código assíncrono achatado em uma forma mais familiar:

 public static async Task Main ()
{ Tarefa  download=DownloadDocsMainPageAsync (); int bytesLoaded=aguardar download; Console.WriteLine ($"{nameof (Main)}: Download de {bytesLoaded} bytes.");
} 

Algumas pessoas muito espertas decidiram que o JavaScript deveria adotar as palavras-chave async... await na linguagem JavaScript. Babel e regenerador transpilaram as construções de palavras-chave em código que usava geradores para atingir o fluxo de trabalho assíncrono. Nodejs deu um passo além e tornou async... await um cidadão de primeira classe.

O que torna o código async... await tão atraente é que ele parece síncrono. O código parece parar e esperar até que uma resposta seja retornada ou um erro ocorra. O código pode ser agrupado em um bloco try..catch familiar.

async... await ganhou muita força, e a revolução do gerador foi esquecida para o mais limitado async... await .

Suspender e retomar

O que torna as funções geradoras de JavaScript tão diferentes é que elas não são executadas inicialmente e, em vez disso, retornam um objeto iterador com uma função next . A execução na função pode ser suspensa e retomada exatamente no ponto em que foi suspensa entre as próximas chamadas.

Tenho usado o pacote npm thefrontside/effection já há algum tempo.

O Effection utiliza a magia dos geradores para nos permitir escrever código como este:

 run (function * () { let socket=new WebSocket ('ws://localhost: 8080'); rendimento throwOnErrorEvent (soquete); rendimento uma vez (soquete,"aberto"); deixe mensagens=produzir uma vez (soquete,"mensagem"); while (true) { deixar mensagem=produzir mensagens.next (); console.log ('Mensagem recebida:', mensagem); }
}); 

Existem algumas abstrações bonitas no código acima que facilitam o caminho para escrever menos código e um código mais simples.

Por exemplo:

 rendimento uma vez (soquete,"abrir"); 

O código acima afirma que a execução não pode prosseguir até que o evento websocket open tenha ocorrido.

Se estivéssemos fazendo isso em JavaScript normal, seria mais ou menos assim:

 const remove=socket.addEventListener ('open', (event)=> { //Continuar
}); 

A essência do gerador

Vamos recapitular rapidamente o que torna os geradores tão poderosos.

Uma função geradora é uma iterator que retorna um objeto que podemos chamar em seguida. Um gerador parece ser uma função, mas se comporta como um iterador .

O que torna os geradores tão poderosos é sua capacidade de suspender e retomar a execução.

A função do gerador everySingleEvenNumber abaixo ilustra esse recurso:

função

 * everySingleEvenNumber () { deixe i=0; while (true) { rendimento i +=2; }
} var gen=everySingleEvenNumber (); console.log (gen.next (). valor);//2
console.log (gen.next (). valor);//4
console.log (gen.next (). valor);//6
console.log (gen.next (). valor);//8 

A construção while (true) parece um loop infinito, mas a execução é suspensa após cada yield e somente retomada quando o iterador próximo função é chamada no código console.log .

O valor atual da variável local i não é redefinido entre cada chamada e é mantido.

Os geradores diferem de async/await, em que a execução desaparece e só retorna quando uma promessa é resolvida ou rejeitada.

Geradores como threads de execução

A capacidade de suspender e retomar funções abre muito mais portas do que async/await fechou em sua rápida adoção.

effection permite que você crie processos separados como funções geradoras e cuide da desmontagem de todos os processos filho iniciados com effection. Esta técnica é conhecida como simultaneidade estruturada .

Effection expõe um objeto task que pode gerar novos processos desanexados :

 main (função * (tarefa: Tarefa) { console.log ('em principal'); task.spawn (function * () { while (true) { produzir sono (100); console.log ('acordado'); } }); produção;
}) 

Cenário do mundo real

Abaixo está uma função flakyConnection que não se conectará até a quinta tentativa:

 deixe tentar=1; function flakyConnection (): Promise <{conectado: booleano}> { retornar nova promessa <{conectado: booleano}> ((resolver)=> { setTimeout (()=> { tentativa ++; resolver ({conectado: tentativa===5}); }, 100); });
} 

Para obter uma conexão, o cliente terá que tentar cinco vezes antes de obter sucesso. Um bom código de cliente também incluirá um tempo limite e lançará uma exceção se a operação demorar muito.

Escrever código de sondagem que atinge o tempo limite é um código chato de escrever, mas a efetividade e as qualidades de suspensão e retomada dos geradores tornam essa experiência muito boa:

 main (função * (pai: Tarefa) { parent.spawn (function * (child) { child.spawn (function * () { console.log ('preparado para lançar um erro'); produzir sono (8000); jogue um novo erro ('você está sem tempo! Melhor sorte na próxima vez.'); }); while (true) { console.log (`tentativa de conexão $ {tentativa}...`); const {conectado}=rendimento flakyConnection (); if (conectado) { console.log ('estamos conectados!'); return true; } console.log ('sem charuto, tentamos novamente'); produzir sono (2000); } }); produção;
}); 

Um novo processo é anexado ao objeto de tarefa pai disponibilizado por meio de main .

O código a seguir elegantemente define um tempo limite que lançará uma exceção se o cliente não puder se conectar após 8000 milissegundos:

 child.spawn (function * () { console.log ('preparado para lançar um erro'); produzir sono (8000); jogue um novo erro ('você está sem tempo! Melhor sorte na próxima vez.');
}); 

A função effection sleep suspenderá a execução por 8.000 milissegundos. Se o processo pai ainda existir após 8.000 milissegundos, ele lançará uma exceção.

O código a seguir tentará se conectar em intervalos de 200 milissegundos até ter sucesso:

 while (true) { console.log (`tentativa de conexão $ {tentativa}...`); const {conectado}=rendimento flakyConnection (); if (conectado) { console.log ('estamos conectados!'); return true; } console.log ('sem charuto, tentamos novamente'); produzir sono (300);
} 

Este código acima pode continuar em execução até que ocorra uma conexão ou até que a exceção de tempo limite lance, em cujo estágio o efeito irá fechar todos os processos filho.

A execução do código acima resulta nesta saída:

 preparado para lançar um erro
tentativa de conexão 1...
sem charuto, tentamos de novo
tentativa de conexão 2...
sem charuto, tentamos de novo
tentativa de conexão 3...
sem charuto, tentamos de novo
tentativa de conexão 4...
estamos conectados! 

Aqui está um repo com o código acima.

Você pode verificar se o tempo limite funciona alterando o código de tempo limite para algo assim:

 child.spawn (function * () { console.log ('preparado para lançar um erro'); produzir sono (4000); jogue um novo erro ('você está sem tempo! Melhor sorte na próxima vez.');
}); 

O tempo limite que ocorre resulta nesta saída:

 preparado para lançar um erro
tentativa de conexão 1...
sem charuto, tentamos de novo
tentativa de conexão 2...
sem charuto, tentamos de novo
Erro: você está sem tempo! Melhor sorte da próxima vez. 

É hora da revolução que nunca aconteceu

Ainda uso async/await para tarefas assíncronas simples e únicas sem fluxo de trabalho, mas é um paradigma limitado.

As funções do gerador podem resolver uma série de problemas que nada mais pode. Iniciar e retomar threads de execução é incrivelmente poderoso, e os geradores têm essa funcionalidade integrada e pronta para uso.

Comece! A água está quente.

O post Geradores de JavaScript: The superior async/await apareceu primeiro no LogRocket Blog .

Source link