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

     2023-02-22     210

关键词:

【中文标题】简单的 Node/Express 应用程序,函数式编程方式(如何在 JavaScript 中处理副作用?)【英文标题】:Simple Node/Express app, the functional programming way (How to handle side-effects in JavaScript?) 【发布时间】:2017-12-24 20:50:52 【问题描述】:

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

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

所以...鉴于以下非常简单的示例 Node/Express 应用程序与 MongoDB 数据库,必须实现哪些代码更改才能使这段代码充分反映当前的 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!')
)

【问题讨论】:

这是一个不适定问题。正如你所说,“一旦完全转换为函数式编程,代码会是什么样子”——你问的问题——是没有意义的,因为“没有现实世界的应用程序可以完全避免副作用”。您上面的代码充满了副作用(登录到控制台、发送问候语、连接到数据库)。您已经避免了循环并使用了 .find 和 lambda 函数,因此本质上不是有状态的部分已经可以正常工作。目前尚不清楚您想从答案中得到什么。 (我没有对这个问题投反对票,但这可能就是有人这样做的原因)。 @anandsun 感谢您的反馈。我很乐意编辑问题以使其更好。也许你可以帮我做到这一点。我的印象是我的问题中的代码示例没有反映 JavaScript 函数式编程的最佳实践。我读到的关于 FP 的所有内容都让人觉得您必须使用流和 monad 以及所有其他复杂的东西来处理应用程序中的副作用和状态更改。我想知道在 JavaScript 中实际上是什么样子的。你是说我提供的代码与 JavaScript 中的 FP 一样好? re:“你必须使用流和单子以及所有其他复杂的东西。”哇,不!这是一个常见的误解,它使人们远离 FP,因为他们认为它太复杂了。我喜欢 FP 并在工作中使用 Scala,但我不同意您必须使用 monad 和流。参见例如fp-is-not-the-answer。单子 > 空值。流很好。但首先,将状态性推到代码的边缘,将逻辑拆分为小函数,使用标准库函数,避免函数中的副作用等。 另一件事是这样的问题可能会在 StackExchange 或其他地方取得更大的成功,因为您不是在询问如何让某件事情发挥作用,而是在询问风格方面的建议,这在 *** 上通常不鼓励,因为它是主观的。 @neoflash 本质上,这意味着您使用 thunk(不带参数的函数)推迟不纯计算,围绕它们构建纯函数组合,并将其留给调用者实际执行它们。通过这样做,您推迟了效果,或者更寓言化,您将其推到了应用程序的边缘。 【参考方案1】:

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

例如,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);
;

它可以用来创建这样的路由处理程序:

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() 函数接受对异步操作的函数返回值的承诺,但可以说它不会那么无副作用:

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 的参数的函数是一个生成器,而不是只产生要解析的承诺(就像基于生成器的协程通常做的那样),它可以产生更加通用的承诺来解决或夹头流,用一些包装来区分两者。这只是一个基本概念,但可以进一步扩展。

【讨论】:

这个答案不能解决我的具体问题,但它得到了一个很棒的答案! 我使用这种方法编写 API 处理程序来服务 PATCH 请求并将它们转换为相应的猫鼬(子)文档更新。所有的处理程序都是纯粹的功能;它们在一个微型框架下运行: * 将 mongoose 子文档转换为 POJO * 调用相应的处理程序 * 获取所需的更改 * 将更改应用到 mongoose 子文档 * 并保存它 这种方式唯一的非 FP 部分是一个微型框架;所有各种处理程序都是纯粹的 FP。

Node (Express) / React 应用程序仅在本地工作

】Node(Express)/React应用程序仅在本地工作【英文标题】:Node(Express)/Reactapponlyworkslocally【发布时间】:2018-02-0818:33:30【问题描述】:问题大纲我有一个简单的Node/React应用程序,使用herokulocal运行良好。Node后端有一个/api/users端点,... 查看详情

MongoDB/Express/Node 文件上传错误

...,以帮助我了解全栈项目的各个部分如何组合在一起。该应用程序非常简单:快速服务器发送一个页面,用户可以使用该页面上传图片。图片已保存,用户可以使用该网站查看上传的图片并查看各种统计信息。 查看详情

node+express实现简单的增删改查

varexpress=require(‘express‘);varbodyParser=require("body-parser");varcors=require(‘cors‘);varapp=express();varmysql=require(‘mysql‘);varconnection=mysql.createPool({connectionLimit:10,host:‘localhost 查看详情

node+express+static完成简单的文件下载

不多说什么,直接上代码varexpress=require(‘express‘);varfs=require(‘fs‘)varpath=require(‘path‘);varcors=require(‘cors‘);varapp=express();app.use(cors());varoptions={dotfiles:‘ignore‘,etag:false,extensions:[‘htm‘, 查看详情

在 Node/Express 中续集 - “没有这样的表:main.User”错误

...描述】:我正在尝试使用Sequelize构建一个简单的Node/Express应用程序,但是当我尝试在我的关系数据库中创建新记录时,我收到了错误UnhandledrejectionSequ 查看详情

简单聊聊:函数式编程

函数式编程(FunctionalProgramming)是一种以函数为基础的编程方式和代码组织方式,能够带来更好的代码调试及项目维护的优势。本篇主要结合笔者在实际项目开发中的一些应用,简要谈谈函数式编程。函数在函数式编程中,任何代... 查看详情

从运行在不同端口的 Vue 应用程序调用 node express 服务器 API

】从运行在不同端口的Vue应用程序调用nodeexpress服务器API【英文标题】:CallingnodeexpressserverAPIfromVueapplicationrunninginadifferentport【发布时间】:2018-03-2906:24:59【问题描述】:我最近开始学习vue.js,目前使用vue.js和vue-simple-webpack模板... 查看详情

函数式接口&lambda表达式——简单应用笔记

以下是根据资料和实验得出的理解:1、Lambda表达式的使用与函数式接口有关2、函数式接口有且仅有一个抽象方法函数式接口、Lambda表达式不懂的可以去参考其他文章以下代码可以自行复制粘贴看看执行效果//函数式接口接口内仅... 查看详情

函数式接口&lambda表达式——简单应用笔记

以下是根据资料和实验得出的理解:1、Lambda表达式的使用与函数式接口有关2、函数式接口有且仅有一个抽象方法函数式接口、Lambda表达式不懂的可以去参考其他文章以下代码可以自行复制粘贴看看执行效果//函数式接口接口内仅... 查看详情

使用 Node/Express 构建企业应用程序

】使用Node/Express构建企业应用程序【英文标题】:BuildingenterpriseappwithNode/Express【发布时间】:2017-06-1201:14:15【问题描述】:我正在尝试了解如何使用Node/Express/Mongo(实际上使用MEAN堆栈)构建企业应用程序。在阅读了2本书和一些... 查看详情

Node、Express、Ajax 和 Jade 示例

】Node、Express、Ajax和Jade示例【英文标题】:Node,Express,Ajax,andJadeExample【发布时间】:2012-07-2715:42:48【问题描述】:我正在寻找一个使用Ajax调用更新Node/Express/Jade页面的简单示例,其中包含客户端和服务器端代码。我很难把所有这... 查看详情

node+express4实现简单文件上传

 varexpress=require(‘express‘);varmultiparty=require(‘multiparty‘);varapp=express();/*文件上传*/app.post(‘/uploadimg‘,function(req,res,next){//生成multiparty对象,并配置上传目标路径varform=newmultiparty.Form({uplo 查看详情

使用 node express 生成器配置 socket.io

...按照thethinksterpost开始的。我已经设法构建了一个简单的应用程序,现在我正在尝试配置socket.io。socket.io初始化代码和事件处理程序并不难理解,真正让我困 查看详情

函数式语言的简单时序分析器

】函数式语言的简单时序分析器【英文标题】:Simpletimingprofilerforfunctionallanguages【发布时间】:2012-08-1418:59:47【问题描述】:我需要一个简单的时序分析器来估计我的程序某些部分的运行时间(用OCaml编写,但我相信这可以适用... 查看详情

node——express中间件原理(代码片段)

functionexpress()varfuncs=[];//待执行的函数数组varapp=function(req,res)vari=0;functionnext()vartask=funcs[i++];//取出函数数组里的下一个函数if(!task)//如果函数不存在,returnreturn;task(req,res,next);//否则,执行下一个函数next();app.u 查看详情

一个简单的node.js

varexpress=require(‘./node_modules/express‘)varapp=express()//设置跨域访问app.all(‘*‘,function(req,res,next) res.header(‘Access-Control-Allow-Origin‘,‘*‘) res.header(‘Access-Control-Allow-Headers‘,‘X-Reque 查看详情

一个简单的node.js

varexpress=require(‘./node_modules/express‘)varapp=express()//设置跨域访问app.all(‘*‘,function(req,res,next) res.header(‘Access-Control-Allow-Origin‘,‘*‘) res.header(‘Access-Control-Allow-Headers‘,‘X-Reque 查看详情

node+express搭建个人网站(代码片段)

我的个人网站 http://yangchaojie.top/首先了解一下nodeNode.js是一个基于ChromeV8引擎的JavaScript运行环境。 Node.js使用了一个事件驱动、非阻塞式I/O的模型,使其轻量又高效。 Node.js的包管理器npm,是全球最大的开源库生态系统... 查看详情