sql >> Base de Datos >  >> NoSQL >> MongoDB

Aplicación Simple Node/Express, la forma de programación funcional (¿Cómo manejar los efectos secundarios en JavaScript?)

No podrá evitar los efectos secundarios por completo, pero puede hacer un esfuerzo para abstraerlos al máximo cuando sea posible.

Por ejemplo, el marco Express es inherentemente imperativo. Ejecutas funciones como res.send() enteramente por sus efectos secundarios (ni siquiera le importa su valor de retorno la mayor parte del tiempo).

Lo que podrías hacer (además de usar const para todas sus declaraciones, use Immutable.js estructuras de datos, Ramda , escribiendo todas las funciones como const fun = arg => expression; en lugar de const fun = (arg) => { statement; statement; }; etc.) sería hacer una pequeña abstracción sobre cómo funciona Express normalmente.

Por ejemplo, podría crear funciones que tomen req como parámetro y devuelve un objeto que contiene el estado de la respuesta, los encabezados y una secuencia que se canalizará como cuerpo. Esas funciones podrían ser funciones puras en el sentido de que su valor de retorno depende solo de su argumento (el objeto de solicitud), pero aún necesitaría algún contenedor para enviar la respuesta utilizando la API inherentemente imperativa de Express. Puede que no sea trivial, pero se puede hacer.

Como ejemplo, considere esta función que toma cuerpo como un objeto para enviar como json:

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

Podría usarse para crear controladores de ruta como este:

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

usando una función que devuelve una sola expresión sin efectos secundarios.

Ejemplo 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);

Probando la respuesta:

$ 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}

Por supuesto, esto es solo una idea básica. Podrías hacer el wrap() la función acepta promesas para el valor de retorno de las funciones para operaciones asíncronas, pero podría decirse que no estará tan libre de efectos secundarios:

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

y un controlador:

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 },
  }))));

Usé .then() en lugar de async /await en el propio controlador para que parezca más funcional, pero se puede escribir como:

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

Podría hacerse aún más universal si la función que es un argumento para wrap sería un generador que, en lugar de generar solo promesas para resolver (como suelen hacer las corrutinas basadas en generadores), generaría promesas para resolver o chucks para transmitir, con algunos ajustes para distinguir los dos. Esta es solo una idea básica, pero puede extenderse mucho más.