简短回答:否 - 不可能。
说明:
在函数式编程的上下文中,我们有一个数据流 - 程序获取一些输入数据,对其进行转换并返回输出数据。
如果是服务器,我们有两个数据流。首先是当您启动服务器时。在此流程中,您可能希望从外部世界读取配置文件或命令行参数,以获取端口、主机、数据库字符串等内容。这是一个副作用,因此我们通常会将其放在 Future 中。例如,
readJson(process.argv[2]) // Read configuration file
.chain(app) // Get app instance (routes, middlewares, etc.)
.chain(start) // Start server
.run().promise()
.then((server) => info(`Server running at: ${server.info.uri}`))
.catch(error);
这是包含所有副作用(读取配置)的典型 index.js 文件。现在让我们转向第二个数据流。
这个数据流有点难以想象。第一个数据流的输出/副作用是服务器在某个端口上侦听外部连接。 现在想象一下,到达此服务器的每个请求本身都是一个独立的数据流。
就像 index.js 是用于处理所有副作用的文件一样,您的路由文件或路由处理程序函数旨在处理副作用,即结果请求应回复至这个路由处理程序。通常,接近纯函数式的路线如下所示:
function handler(request, reply) {
compose(serveFile(reply), fileToServe)(request)
.orElse((err) => err.code === 'ENOENT' ? reply404(reply) : reply500(reply))
.run(); // .run() is the side effect
}
return {
method: 'GET',
path: '/employer/{files*}',
handler
};
在上面的sn-p中,一切都是纯净的。只有不纯的东西是 .run() 方法(我正在使用 Hapi.js 和 Folktale.js 任务)。
Angular 或 React 等前端框架的想法也是如此。这些框架中的组件应该包含所有的影响/杂质。这些组件就像您的路由处理程序一样是应该发生副作用的端点。你的模型/服务应该没有杂质。
话虽如此,如果您仍然想让您的路线完全纯净,那么就有希望了。您本质上想要做的是 - 更高级别的抽象:
- 如果 Express.js 是一个框架,那么您应该在它之上构建自己的抽象。
- 您通常会为所有路由编写自己的通用路由处理程序,并且您将公开自己的 API 来注册路由。
- 然后,您的路由处理程序将返回 future/task/observable 或任何其他包含所有等待释放的副作用的 monad。
- 然后您的抽象路由处理程序将简单地调用 .fork() 或 .run()。
- 这意味着当您对路由处理程序进行单元测试时,不会发生任何副作用。它们只是简单地封装在一些 Async Monad 中。
在前端框架的情况下,正如我之前所说,你的 UI 组件会有副作用。但是有一些新的框架超越了这种抽象。 Cycle.js 是我知道的一个框架。它应用了比 Angular 或 React 更高级别的抽象。所有的副作用都被限制在 observable 中,然后被分派到一个框架并执行,使所有(我的字面意思是 100%)你的组件都是纯的。
我尝试按照您使用 Hapi.js+Folktale.js+Ramda 的方式创建服务器。我的想法最初起源于 Cycle.js。然而,我取得了轻微的成功。有些部分代码真的很尴尬,而且限制太多,无法编写。之后,我放弃了纯路线。但是,我的其余代码是纯粹的并且可读性很强。
最后,函数式编程是关于部分编码而不是整体编码。您或我正在尝试做的是从整体上进行函数式编程。 至少在 JavaScript 中这会让人觉得有点尴尬。