As funções generator assíncronas são um novo recurso no ES2018. Node.js adicionou suporte para funções generator assíncrona no Node.js 10. As funções generator assíncrona podem parecer um recurso de nicho bonito, mas apresentam uma ótima oportunidade para estruturar websockets em Node.js. 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 ou Hapi . Em geral, a maioria das estruturas de servidor HTTP se enquadra em uma das 3 classes:
- Resposta explícita - Para enviar uma resposta HTTP no Express, você deve chamar
res.end()
,res.json()
ou alguma outra função no objetores
. Em outras palavras, você precisa chamar explicitamente um método para enviar uma resposta. - Resposta implícita usando
return
- Por outro lado, o Hapi v17 removeu explicitamente a funçãoreply()
. Portanto, Hapi não tem um equivalente ares
: para enviar uma resposta, vocêreturn
um valor de seu manipulador de solicitação. Hapi então converte o valor dereturn
em uma resposta HTTP. - Modifique a resposta no local - Koa usa uma abordagem distinta que é uma mistura das duas anteriores. Em vez de chamar funções
res
, você modifica um objetoctx
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 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 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:
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.
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 :
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.
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:
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 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 é mais portátil e fácil de testar.
Créditos
- Async Generator Functions and Websockets in Node.js, escrito originalmente por Valeri Karpov.
Top comments (0)