sno*_*dev 13 javascript functional-programming mongodb node.js express
关于JavaScript中的函数式编程理论有很多很好的文章.有些甚至包含代码示例,显示了命令式/面向对象编程和声明/函数式编程之间的区别.但我发现没有一个能够通过简单的JavaScript代码示例来展示如何处理Web应用程序中的副作用.没有现实世界的应用程序可以完全避免的副作用(数据库调用,登录到控制台,保存到一个文件,绘制到屏幕等),我有一个很难弄清楚它是如何在实践中完成的.
有博客文章和S/O答案(如下所示:如何在纯函数式编程中执行副作用?)触及现实世界中处理副作用的主题,但它们通常远非简单,不要包括代码示例或包含其他语言的代码示例(Haskell,Scala等).我没有为Node/JavaScript找到一个.
所以...给出以下非常简单的带有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);
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!')
})
Run Code Online (Sandbox Code Playgroud)
rsp*_*rsp 27
您将无法完全避免副作用,但您可以尽一切努力在可能的情况下最大限度地抽象它们.
例如,Express框架本质上是必要的.你可以res.send()完全运行它们的副作用(你大多数时候都不关心它的返回值).
您可以做什么(除了使用const所有声明,使用Immutable.js数据结构,Ramda,编写所有函数const fun = arg => expression;而不是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);
};
Run Code Online (Sandbox Code Playgroud)
它可以用来创建这样的路由处理程序:
app.get('/sum/:x/:y', wrap(req => ({
headers: { 'Foo': 'Bar' },
body: { result: +req.params.x + +req.params.y },
})));
Run Code Online (Sandbox Code Playgroud)
使用一个返回单个表达式而没有副作用的函数.
完整的例子:
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);
Run Code Online (Sandbox Code Playgroud)
测试响应:
$ 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}
Run Code Online (Sandbox Code Playgroud)
当然这只是一个基本想法.你可以让wrap()函数接受对异步操作的函数返回值的promises,但是它可以说不是那么副作用免费:
const wrap = f => async (req, res) => {
const { status = 200, headers = {}, body = {} } = await f(req);
res.status(status).set(headers).json(body);
};
Run Code Online (Sandbox Code Playgroud)
和处理程序:
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 },
}))));
Run Code Online (Sandbox Code Playgroud)
我在处理程序本身中使用.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) },
})));
Run Code Online (Sandbox Code Playgroud)
如果作为参数的函数wrap将是一个生成器,而不是只产生许可来解决(比如基于生成器的协同程序通常会这样做),它可能会产生更多的通用性,它会产生解决或chucks流的承诺,一些包装来区分两者.这只是一个基本想法,但它可以进一步扩展.
| 归档时间: |
|
| 查看次数: |
4841 次 |
| 最近记录: |