MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Aplicativo Node/Express simples, a maneira de programação funcional (como lidar com efeitos colaterais em JavaScript?)


Você não será capaz de evitar totalmente os efeitos colaterais, mas pode fazer algum esforço para abstraí-los ao máximo sempre que possível.

Por exemplo, a estrutura Express é inerentemente imperativa. Você executa funções como res.send() inteiramente por seus efeitos colaterais (você nem se importa com o valor de retorno na maioria das vezes).

O que você pode fazer (além de usar const para todas as suas declarações, usando Immutable.js estruturas de dados, Ramda , escrevendo todas as funções como const fun = arg => expression; em vez de const fun = (arg) => { statement; statement; }; etc.) seria fazer uma pequena abstração sobre como o Express normalmente funciona.

Por exemplo, você pode criar funções que usam req como parâmetro e retornar um objeto que contém status de resposta, cabeçalhos e um fluxo a ser canalizado como corpo. Essas funções podem ser funções puras no sentido de que seu valor de retorno depende apenas de seu argumento (o objeto de solicitação), mas você ainda precisa de algum wrapper para enviar a resposta usando a API inerentemente imperativa do Express. Pode não ser trivial, mas pode ser feito.

Como exemplo considere esta função que recebe body como um objeto para enviar como json:
const wrap = f => (req, res) => {
  const { status = 200, headers = {}, body = {} } = f(req);
  res.status(status).set(headers).json(body);
};

Ele pode ser usado para criar manipuladores de rotas como este:
app.get('/sum/:x/:y', wrap(req => ({
  headers: { 'Foo': 'Bar' },
  body: { result: +req.params.x + +req.params.y },
})));

usando uma função que retorna uma única expressão sem efeitos colaterais.

Exemplo completo:
const app = require('express')();

const wrap = f => (req, res) => {
  const { status = 200, headers = {}, body = {} } = f(req);
  res.status(status).set(headers).json(body);
};

app.get('/sum/:x/:y', wrap(req => ({
  headers: { 'Foo': 'Bar' },
  body: { result: +req.params.x + +req.params.y },
})));

app.listen(4444);

Testando a resposta:
$ curl localhost:4444/sum/2/4 -v
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 4444 (#0)
> GET /sum/2/4 HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:4444
> Accept: */*
> 
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Foo: Bar
< Content-Type: application/json; charset=utf-8
< Content-Length: 12
< ETag: W/"c-Up02vIPchuYz06aaEYNjufz5tpQ"
< Date: Wed, 19 Jul 2017 15:14:37 GMT
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact
{"result":6}

Claro que isso é apenas uma ideia básica. Você pode fazer o wrap() function aceita promessas para o valor de retorno das funções para operações assíncronas, mas provavelmente não será tão livre de efeitos colaterais:
const wrap = f => async (req, res) => {
  const { status = 200, headers = {}, body = {} } = await f(req);
  res.status(status).set(headers).json(body);
};

e um manipulador:
const delay = (t, v) => new Promise(resolve => setTimeout(() => resolve(v), t));

app.get('/sum/:x/:y', wrap(req =>
  delay(1000, +req.params.x + +req.params.y).then(result => ({
    headers: { 'Foo': 'Bar' },
    body: { result },
  }))));

Eu usei .then() em vez de async /await no próprio manipulador para torná-lo mais funcional, mas pode ser escrito como:
app.get('/sum/:x/:y', wrap(async req => ({
  headers: { 'Foo': 'Bar' },
  body: { result: await delay(1000, +req.params.x + +req.params.y) },
})));

Poderia ser ainda mais universal se a função que é um argumento para wrap seria um gerador que, em vez de render apenas promessas de resolução (como as corrotinas baseadas em gerador geralmente fazem), renderia promessas de resolução ou chucks para transmitir, com algum encapsulamento para distinguir os dois. Esta é apenas uma ideia básica, mas pode ser estendida muito mais.