简单的Node / Express应用程序,函数式编程方式(如何处理JavaScript中的副作用?) [英] Simple Node/Express app, the functional programming way (How to handle side-effects in JavaScript?)

查看:170
本文介绍了简单的Node / Express应用程序,函数式编程方式(如何处理JavaScript中的副作用?)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在JavaScript中有许多关于函数式编程理论的优秀文章。有些甚至包含代码示例,显示命令式/面向对象编程与声明式/函数式编程之间的区别。但我没有发现任何显示,用简单的JavaScript代码示例显示如何处理Web应用程序中的副作用。没有现实世界的应用程序可以完全避免副作用(数据库调用,登录到控制台,保存到文件,绘图到屏幕等),我很难弄清楚它是如何在实践中完成的。



有博客文章和S / O答案(就像这样:

因此...给出以下非常简单的使用MongoDB数据库的Node / Express应用程序示例,必须实施哪些代码更改这段代码完全反映了当前JavaScript函数式编程的最佳实践。特别是当涉及处理数据库调用的路由/函数时。我希望你的回答能够帮助我和其他人更好地理解实际JavaScript中的避免副作用功能编程概念的实际应用。

  / * app.js * / 

const express = require('express')
const app = express()
var mongoose = require('mongoose');
mongoose.connect('mongodb:// localhost / test');

var greetingSchema = mongoose.Schema({
greeting:String
});

var Greeting = mongoose.model('Greeting',greetingSchema);
$ b $ app.get('/',function(req,res){
Greeting.find({greeting:'Hello World!'},function(err,greeting){
res.send(greeting);
});
});
$ b $ app.post('/',function(req,res){
Greeting.create({greeting:'Wasssssssssssuuuppppp'},function(err,greeting){
res.send(greeting);
});
});
$ b app.listen(3000,function(){
console.log('Example app listening on port 3000!')
})


您将无法完全避免副作用,但您可以尽一切努力最大限度地抽象他们在可能的情况下离开。



例如,Express框架固有地势在必行。你的副作用完全可以像 res.send()那样运行(你甚至不关心它的大部分时间返回值)。



您可以使用 const .io / immutable-js /rel =noreferrer> Immutable.js 数据结构, Ramda ,将所有函数写成 const fun = arg =>表达式; 而不是 const fun =(arg)=> {statement; statement;};

等)将会对Express的运行方式进行一些抽象。例如,你可以创建一些函数,使用 req 作为参数,并返回一个包含响应状态,标题和要作为正文管道的流的对象。这些函数可能是纯函数,因为它们的返回值仅取决于它们的参数(请求对象),但您仍然需要一些包装来实际发送使用Express的固有命令API的响应。它可能不是微不足道的,但它可以做到。



作为一个例子,考虑这个函数将body作为一个对象发送为json:

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

使用一个函数返回一个没有副作用的表达式。



完整示例:

  const app = require('express')(); 

const wrap = f => (req,res)=> {
const {status = 200,headers = {},body = {}} = f(req);
res.status(status).set(headers).json(body);
};
$ b $ app.get('/ sum /:x /:y',wrap(req =>({
headers:{'Foo':'Bar'},
body:{result:+ req.params.x + + req.params.y},
})));

app.listen(4444);

测试回应:

  $ curl localhost:4444 / sum / 2/4 -v 
*在DNS缓存中找不到主机名
*尝试127.0.0.1 ...
*已连接到本地主机(127.0.0.1)端口4444(#0)
> GET / sum / 2/4 HTTP / 1.1
> User-Agent:curl / 7.35.0
>主机:localhost:4444
>接受:* / *
>
< HTTP / 1.1 200 OK
< X-Powered-By:Express
< Foo:Bar
< Content-Type:application / json; charset = utf-8
<内容长度:12
< ETag:W /c-Up02vIPchuYz06aaEYNjufz5tpQ
<日期:2017年7月19日星期三15:14:37 GMT
<连接:保持活动
<
*连接#0到主机localhost保持完好
{result:6}



当然,这只是一个基本的想法。你可以使 wrap()函数接受承诺函数作为异步操作函数的返回值,但是可以证明它不会如此副作用:

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

和处理程序:

  const delay =(t,v)=> new Promise(resolve => setTimeout(()=> resolve(v),t)); 
$ b app.get('/ sum /:x /:y',wrap(req =>
delay(1000,+ req.params.x + + req.params.y ).then(result =>({
headers:{'Foo':'Bar'},
body:{result},
}))));

我用 .then()代替 async / await 在处理程序中使它看起来更加实用,但它可以写成:



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

如果函数是的参数, wrap 将是一个生成器,它不仅仅产生解决的承诺(就像基于生成器的协程通常所做的那样),它会产生承诺解析或者流式传输,用一些包装来区分这两者。这只是一个基本的想法,但可以进一步扩展。

There are many good articles about the theory of functional programming in JavaScript. Some even contain code examples showing the difference between imperative/object-oriented programming and declarative/functional programming. But I have found none that show, with simple JavaScript code examples, how to handle side-effects in a web app. No real world application can entirely avoid side-effects (database calls, logging to console, saving to a file, drawing to the screen etc.) and I have a hard time figuring out how it is done in practice.

There are blog articles and S/O answers (like this one: How to perform side-effects in pure functional programming?) that touch on the subject of handling side-effects in the real world but they are usually far from simple, don't include code example or include code example in other languages (Haskell, Scala, etc.). I haven't found one for Node/JavaScript.

So... given the following very simple example Node/Express app with MongoDB database, what code changes must be implemented so that this piece of code fully reflect current JavaScript functional programming best practices. Especially when it comes to the routes/functions handling database calls. I'm hoping your answers will help me, and others, better understand the practical application of the 'avoiding side-effects' concept of Functional Programming in real-world JavaScript.

/*app.js*/

const express = require('express')
const app = express()
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');

var greetingSchema = mongoose.Schema({
    greeting: String
});

var Greeting = mongoose.model('Greeting', greetingSchema);

app.get('/', function (req, res) {
  Greeting.find({greeting: 'Hello World!'}, function (err, greeting){
    res.send(greeting);
  });  
});

app.post('/', function (req, res) {
  Greeting.create({greeting: 'Wasssssssssssuuuuppppp'}, function (err, greeting){
  res.send(greeting);
  });      
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

解决方案

You will not be able to avoid side effects entirely but you can make some effort to maximally abstract them away where possible.

For example the Express framework is inherently imperative. You run functions like res.send() entirely for their side effects (you don't even care about its return value most of the time).

What you could do (in addition to using const for all your declarations, using Immutable.js data structures, Ramda, writing all functions as const fun = arg => expression; instead of const fun = (arg) => { statement; statement; }; etc.) would be to make a little abstraction on how Express usually works.

For example you could create functions that take req as parameter and return an object that contains response status, headers and a stream to be piped as body. Those functions could be pure functions in a sense that their return value depend only on their argument (the request object) but you would still need some wrapper to actually send the response using the inherently imperative API of Express. It may not be trivial but it can be done.

As an example consider this function that takes body as an object to send as json:

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

It could be used to create route handlers like this:

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

using a function that returns a single expression with no side effects.

Complete example:

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

Testing the response:

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

Of course this is just a basic idea. You could make the wrap() function accept promises for the return value of the functions for async oprations, but then it will arguably not be so side-effect free:

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

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

I used .then() instead of async/await in the handler itself to make it look more functional, but it can be written as:

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

It could be made even more universal if the function that is an argument to wrap would be a generator that instead of yielding only promises to resolve (like the generator-based coroutines usually do) it would yield either promises to resolve or chucks to stream, with some wrapping to distinguish the two. This is just a basic idea but it can be extended much further.

这篇关于简单的Node / Express应用程序,函数式编程方式(如何处理JavaScript中的副作用?)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆