反应式编程最简短的介绍

本文的目的是说明为什么需要反应式编程,它与函数式编程之间的关系,以及如何使用它编写易于适应新要求的声明性代码。 另外,我想通过一个接近真实的示例来尽可能简短地做到这一点。


执行以下任务:
REST API和终结点/people提供了某些服务。 对此端点的POST请求将创建一个新实体。 编写一个函数,该函数采用{ name: 'Max' }形式的对象数组,并使用API​​创建一组实体(英文,这称为批处理操作)。


让我们以命令式的方式解决此问题:


 const request = require('superagent') function batchCreate(bodies) { const calls = [] for (let body of bodies) { calls.push( request .post('/people') .send(body) .then(r => r.status) ) } return Promise.all(calls) } 

为了进行比较,让我们以功能样式重写这段代码。 为了简单起见,我们用功能样式表示:


  1. 使用功能性原语( .map.filter.reduce )代替命令式循环( forwhile
  2. 该代码被组织为“纯”函数-它们仅取决于参数,而不取决于系统状态

功能样式代码:


 const request = require('superagent') function batchCreate(bodies) { const calls = bodies.map(body => request .post('/people') .send(body) .then(r => r.status) ) return Promise.all(calls) } 

我们得到了一段相同大小的代码,值得一提的是,尚不清楚这一段代码是否比上一段更好。
为了理解为什么第二段代码更好-您需要开始更改代码,想象一下原始任务出现了新要求:
我们调用的服务在一定时间内限制了请求的数量:一秒钟内,一个客户端最多可以执行五个请求。 执行更多请求将导致服务返回429 HTTP错误(请求过多)。


在这一点上,可能值得停止并尝试自己解决问题,%用户名%


让我们以功能代码为基础,并尝试对其进行更改。 “纯”函数式编程的主要问题是它对运行时和输入输出“一无所知”(英语中有一个表示副作用的表达),但实际上我们一直在与它们合作。
为了填补这一空白,Reactive Programming可以提供帮助-一组尝试解决副作用问题的方法。 该范例最著名的实现是使用反应流概念的Rx库。


什么是反应流? 如果时间很短,那么这是一种允许您将功能性原语(.map,.filter,.reduce)应用于随时间分布的对象的方法。


例如,我们通过网络发送一组特定的命令-我们不需要等到获得完整的命令集,就可以将其呈现为响应流,然后就可以使用它了。 这里还有另外两个重要概念:


  • 流量可以是无限的,也可以随时间任意分配
  • 仅当接收方准备好处理它时,发送方才发送命令(背压)

本文的目的是找到简单的方法,因此,我们将采用Highland库,该库试图解决与Rx相同的问题,但是更容易学习。 里面的想法很简单:让我们以Node.js流为基础,然后将数据从一个Stream传输到另一个Stream。


让我们开始:让我们从一个简单的例子开始-我们将使代码“具有反应性”而不添加任何新功能


 const request = require('superagent') const H = require('highland') function batchCreate(bodies) { return H(bodies) .flatMap(body => H(request .post('localhost:3000/people') .send(body) .then(r => r.status) ) ) .collect() .toPromise(Promise) } 

您要注意的是:


  • H(实体)-我们从数组创建流
  • .flatMap及其接受的回调。 这个想法很简单-我们将Promise包装在流构造器中,以便获得具有一个值(或错误)的流。重要的是要了解这是值,而不是Promise。
    结果,这给了我们一系列的流-使用flatMap将其平滑为一个可以对其进行操作的值流(谁说的monad?)
  • .collect-我们需要将一个点中的所有值收集到一个数组中
  • .toPromise-将返回给我们Promise,当我们从流中获得价值时,该承诺将实现

现在,让我们尝试实现我们的要求:


 const request = require('superagent') const H = require('highland') function batchCreate(bodies) { return H(bodies) .flatMap(body => H(request .post('localhost:3000/people') .send(body) .then(r => r.status) ) ) .ratelimit(5, 1000) .collect() .toPromise(Promise) } 

由于背压的概念,在此范例中,这只是一行.ratelimit。 在Rx中,它占用的空间大约相同


好了,您的意见很有趣:


  • 我能否达到本文开头所声明的结果?
  • 使用命令式方法能否获得相似的结果?
  • 您对反应式编程感兴趣吗?

PS:在这里您可以找到我的另一篇有关响应式编程的文章

Source: https://habr.com/ru/post/zh-CN427467/


All Articles