javascript - 简单的 Node/Express 应用程序,函数式编程方式(如何在 JavaScript 中处理副作用?)

标签 javascript node.js mongodb express functional-programming

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

有博客文章和 S/O 答案(例如:How to perform side-effects in pure functional programming?)涉及在现实世界中处理副作用的主题,但它们通常远非简单,不包含代码示例或包括其他语言(Haskell、Scala 等)的代码示例。我还没有找到适用于 Node/JavaScript 的。

所以...给定以下非常简单的带有 MongoDB 数据库的 Node/Express 应用程序示例,必须实现哪些代码更改才能使这段代码充分反射(reflect)当前的 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);

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!')
})

最佳答案

您将无法完全避免副作用,但您可以尽最大努力将它们尽可能地抽象出来。

例如,Express 框架本质上是命令式的。您运行像 res.send() 这样的函数完全是因为它们的副作用(大多数时候您甚至都不关心它的返回值)。

你可以做什么(除了对所有声明使用 const,使用 Immutable.js 数据结构,Ramda ,将所有函数编写为 const fun = arg => 表达式; 而不是 const fun = (arg) => { statement; statement; }; etc.) 将对 Express 通常的工作方式进行一些抽象。

例如,您可以创建以 req 作为参数的函数,并返回一个对象,该对象包含响应状态、 header 和作为主体通过管道传输的流。从某种意义上说,这些函数可能是纯函数,它们的返回值仅取决于它们的参数(请求对象),但您仍然需要一些包装器才能使用 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);
};

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

当然这只是一个基本的想法。您可以使 wrap() 函数接受对异步操作的函数返回值的 promise ,但可以说它不会那么无副作用:

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

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 以使其看起来更具功能性,但也可以写成:

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

如果作为 wrap 参数的函数将是一个生成器,而不是只产生解决的 promise (就像基于生成器的协程通常做的那样),它可以变得更加通用yield 要么 promises to resolve 要么 chucks to stream ,并通过一些包装来区分两者。这只是一个基本概念,但可以进一步扩展。

关于javascript - 简单的 Node/Express 应用程序,函数式编程方式(如何在 JavaScript 中处理副作用?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45192989/

相关文章:

javascript - 如何同时使用 jquery 动画效果

javascript - 如何制作一个4步的范围 slider ?

javascript - 将 Observables 实现到持久队列库中

java - 带有 ObjectID 的 Spring Data Mongo 自定义存储库查询

mongodb - 如何将 azure blob 自定义元数据字段索引到 Azure 搜索

javascript - 可视化来自远程对等连接的媒体流

javascript - 如何使用 Angularjs 和 NodeJs 在 ckeditor 中上传图像

node.js - 如何在CubeJS的JSON查询中按度量返回COUNT条记录

node.js - 是否可以在nodejs Async( waterfall ,系列等...)中构建动态任务列表

javascript - 如何从 javascript 代码调用外部 java 函数?