Node.js - Funções Generators Assíncronas e Websockets
Tags: ptbr, node, javascript, webdev
Published: 2020-10-22 02:13:00

[As funções generator assíncronas](https://dev.to/oieduardorabelo/javascript-funcoes-generator-assincronas-5eb0) são um novo recurso no [ES2018](https://2ality.com/2016/10/asynchronous-iteration.html). Node.js adicionou suporte para funções generator assíncrona no [Node.js 10](https://medium.com/@nairihar/async-iteration-in-nodejs-v10-3c17dc00ed9f). As funções generator assíncrona podem parecer um recurso de nicho bonito, mas apresentam uma ótima oportunidade para estruturar [websockets em Node.js](https://dev.to/oieduardorabelo/node-js-usando-websockets-1k9j). Neste artigo, explicarei como uma estrutura de websocket Node.js pode usar funções generator assíncronas.

## Classificando frameworks HTTP

Primeiro, pense em frameworks de servidor HTTP, como [Express](https://masteringjs.io/express) ou [Hapi](https://hapi.dev/) . Em geral, a maioria das estruturas de servidor HTTP se enquadra em uma das 3 classes:

1.  **Resposta explícita** - Para enviar uma resposta HTTP no Express, você deve chamar `res.end()`, `res.json()` ou alguma outra função no [objeto `res`](https://masteringjs.io/tutorials/express/res). Em outras palavras, você precisa chamar explicitamente um método para enviar uma resposta.
2.  **Resposta implícita usando `return`** - Por outro lado, o [Hapi v17 removeu explicitamente a função `reply()`](https://auth0.com/blog/developing-restful-apis-with-hapijs/#Hapi-v17--What-s-New-). Portanto, Hapi não tem um equivalente a `res`: para enviar uma resposta, você `return` um valor de seu manipulador de solicitação. Hapi então converte o valor de `return` em uma resposta HTTP.
3.  **Modifique a resposta no local** - [Koa](https://koajs.com/) usa uma abordagem distinta que é uma mistura das duas anteriores. Em vez de chamar funções `res`, você modifica um objeto `ctx` para estruturar sua resposta.

Em outras palavras, alguns frameworks HTTP fazem com que você chame explicitamente uma função para enviar a resposta HTTP, alguns fornecem um objeto de resposta HTTP para modificar e alguns apenas pegam o valor de `return` de uma função manipuladora de requisição.

A diferença entre websockets e HTTP é que o servidor pode enviar mensagens para o socket sempre que quiser, seja em resposta a uma mensagem ou não. Isso significa que estruturas de websocket de baixo nível como [ws](https://www.npmjs.com/package/ws) se parecem muito com o padrão de "resposta explícita": você precisa chamar explicitamente uma função para enviar uma mensagem.

Mas você poderia fazer algo como resposta implícita com websockets, mantendo o benefício de poder enviar várias mensagens? É aí que entram os geradores assíncronos.

## Lendo Pedaços de Informação no Servidor

Suponha que você tenha um [cursor Mongoose](http://thecodebarbarian.com/cursors-in-mongoose-45) que lê um monte de documentos, um de cada vez, e deseja enviar cada documento por um websocket assim que o cursor o ler. Isso pode ser útil se você quiser minimizar a quantidade de memória que seu servidor usa a qualquer momento: o cliente obtém todos os dados, mas o servidor nunca precisa manter todos os dados em memória de uma só vez. Por exemplo, veja como você pode ler um cursor usando async/await:

```javascript
const User = mongoose.model('User', mongoose.Schema({ name: String }));

const cursor = Model.find().cursor();
for await (const doc of cursor) {
  console.log(doc.name); // Imprime os nomes 1 a 1.
}
```

O que torna os generators tão interessantes é que `yield` são como um `return`, exceto que uma função pode fazer `yield` várias vezes e continuar de onde parou. Portanto, uma função de gerador assíncrono pode fazer várias respostas implícitas.

```javascript
const User = mongoose.model('User', mongoose.Schema({ name: String }));

async function* streamUsers() {
  const cursor = Model.find().cursor();
  for await (const doc of cursor) {
    // Usando `yield` em cada documento é como usar resposta implícita, caso o
    // framework que você estiver usando suportar essa sintaxe
    yield doc;
  }
}
```

[Veja como você pode construir um servidor websocket com Node.js](https://masteringjs.io/tutorials/node/websocket-server) :

```javascript
const WebSocket = require('ws');
const server = new WebSocket.Server({
  port: 8080
});

server.on('connection', function(socket) {
  socket.on('message', function(msg) {
    // Trata a mensagem
  });
});
```

Portanto, agora, o truque é colar o servidor websocket a função `streamUsers()`. Suponha que cada mensagem recebida seja um JSON válido e tenha as propriedades `action`e `id`. Quando `action === 'streamUsers'`, você pode chamar `streamUsers()` e enviar todos os usuários para o socket conforme eles saem do cursor Mongoose.

```javascript
const WebSocket = require('ws');
const server = new WebSocket.Server({
  port: 8080
});

server.on('connection', function(socket) {
  socket.on('message', function(msg) {
    msg = JSON.parse(msg);

    if (msg.action === 'streamUsers') {
      void async function() {
        // Envia 1 mensagem por usuário, ao invés de carregar todos os
        // usuários e enviar todos os usuários em 1 mensagem.
        for await (const doc of streamUsers()) {
          socket.send(JSON.stringify({ id: msg.id, doc }));
        }
      }().catch(err => socket.send(JSON.stringify({ id: msg.id, error: err.message })));
    }
  });
});
```

Veja como você chamaria `streamUsers()` por meio do cliente websocket:

```javascript
const client = new WebSocket('ws://localhost:8080');

// Irá imprimir cada usuário, 1 por vez
client.on('message', msg => console.log(msg));

await new Promise(resolve => client.once('open', resolve));

client.send(JSON.stringify({ action: 'streamUsers', id: 1 }));
```

## Finalizando

As funções generators assíncronas fornecem uma oportunidade para criar uma estrutura de websocket de nível superior com base no padrão de resposta implícito que as estruturas HTTP como Hapi e [Fastify](https://www.fastify.io/) usam. O principal benefício do padrão de resposta implícito é que sua lógica de negócios não precisa estar ciente se o framework está enviando o resultado via websocket, pesquisa de HTTP ou outra coisa. [JavaScript sem framework](https://www.getrevue.co/profile/masteringjs/issues/framework-free-javascript-why-it-matters-188138) é mais portátil e fácil de testar.

---

# Créditos

- [Async Generator Functions and Websockets in Node.js](http://thecodebarbarian.com/async-generator-functions-and-websockets-in-node-js/), escrito originalmente por [Valeri Karpov](https://twitter.com/code_barbarian).