MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Einfache Node/Express-App, die funktionale Programmiermethode (Wie gehe ich mit Seiteneffekten in JavaScript um?)

Sie werden Nebenwirkungen nicht ganz vermeiden können, aber Sie können sich bemühen, sie soweit wie möglich zu entfernen.

Beispielsweise ist das Express-Framework von Natur aus zwingend erforderlich. Sie führen Funktionen wie res.send() aus ausschließlich für ihre Nebenwirkungen (Sie kümmern sich meistens nicht einmal um den Rückgabewert).

Was Sie tun könnten (zusätzlich zur Verwendung von const Verwenden Sie für alle Ihre Erklärungen Immutable.js Datenstrukturen, Ramda , wobei alle Funktionen als const fun = arg => expression; geschrieben werden statt const fun = (arg) => { statement; statement; }; usw.) wäre eine kleine Abstraktion darüber, wie Express normalerweise funktioniert.

Beispielsweise könnten Sie Funktionen erstellen, die req annehmen als Parameter und geben ein Objekt zurück, das den Antwortstatus, Header und einen Stream enthält, der als Hauptteil weitergeleitet werden soll. Diese Funktionen könnten reine Funktionen in dem Sinne sein, dass ihr Rückgabewert nur von ihrem Argument (dem Anforderungsobjekt) abhängt, aber Sie würden immer noch einen Wrapper benötigen, um die Antwort tatsächlich mit der von Natur aus zwingenden API von Express zu senden. Es ist vielleicht nicht trivial, aber es ist machbar.

Betrachten Sie als Beispiel diese Funktion, die body als Objekt zum Senden als json:

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

Es könnte verwendet werden, um Routen-Handler wie diesen zu erstellen:

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

Verwendung einer Funktion, die einen einzelnen Ausdruck ohne Seiteneffekte zurückgibt.

Vollständiges Beispiel:

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

Testen der Antwort:

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

Das ist natürlich nur eine Grundidee. Sie könnten den wrap() machen Funktion akzeptieren Versprechungen für den Rückgabewert der Funktionen für asynchrone Operationen, aber dann wird es wohl nicht so frei von Nebenwirkungen sein:

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

und ein Handler:

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

Ich habe .then() verwendet statt async /await im Handler selbst, damit es funktionaler aussieht, aber es kann geschrieben werden als:

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

Es könnte noch universeller gemacht werden, wenn die Funktion, die ein Argument für wrap ist wäre ein Generator, der nicht nur Versprechungen zur Auflösung liefert (wie es die generatorbasierten Coroutinen normalerweise tun), sondern entweder Versprechungen zur Auflösung oder Chucks zum Streamen, mit etwas Wrapping, um die beiden zu unterscheiden. Dies ist nur eine Grundidee, aber sie kann viel weiter ausgebaut werden.