现实生活中的例子反应式编程

时间:2023-02-08 07:59:50


编程教育被动做法 - 一个相当困难的事情,而且缺乏教材只会加剧这一进程。大多数现有的培训手册不提供深入的审查,并讨论如何设计项目作为一个整体的架构。
这种材料的目的是帮助初学者开始思考真正的“反应”。
那么,什么是无编程?
有很多并不完全正确定义,并在互联网上的解释。*给出太微薄描述。对堆栈溢出的答案往往是不可理解的初学者。喷气宣言看起来就像是项目经理或业务写入。从微软接收的术语,其中指出,«RX =观测量+ LINQ +调度»,我们大多数人拥有其中的利害关系知之甚少。术语如“反应”和“变化的传播”不表达什么,从平时的MV *的做法,在无数的语言已经实现不同。任何框架响应模型中的变化。不管是什么框架,可能会改变。如果不是这种情况下,用户将看不到任何变化。
我们给这个术语的详细说明“无编程”。

反应式编程 - 异步数据流编程
然而,没有什么新意。事件bus'y还是普通的单击事件 - 也是异步数据流,你可以听,要采取任何行动作出回应。反应 - 这是同样的想法,建于绝对的。您可以创建不仅从指导事件或单击鼠标数据流。流可以是任何东西:变量,用户输入功能,高速缓存,数据结构等例如,想象一下,在Twitter上你的新闻源 - 事件流。可以听到流,并分别对事件作出反应,。
此外,你会得到一个惊人的功能集的结合,创造和这些流进行过滤。这就是所有的魔术表现这种方法。一个或多个线程可被用作输入到另一个线程。您可以将两个流。你可以只选择那些你感兴趣的事件过滤流。

由于流动 - 在被动方式这一重要的事情,让我们来看看他们在细节上的用户鼠标点击的例子:

流 - 按时间排序的一系列事件。他可以抛出三种类型的数据:(某种类型的)的值,误差或终止信号。完成信号传播时包含按钮当前窗口或窗口,关闭。
 
我们异步截获这些事件,指着当值发射将被调用,其他的失误,第三,完成信号的处理功能。在某些情况下,你可以省略过去两年专注于公告函数截取值。听流称为订阅(订阅)。我们宣布的功能,被称为观察者(Observer)。饲料 - 是我们观测(可观察,观察对象)的对象。这正是所谓的“观察者”的设计模式。了解更多关于设计模式对于初学者来说,阅读的文章。
在本指南中,我们将使用使用ASCII字符呈现如上图的另一种方式

--a---b-c---d---X---|->

A,B,C,D - 生成的值
点¯x - 错误
| - 完成信号
---> - 时间轴



现在让我们从原始数据流转换点击帖子的新流。



要开始使计数器流,它决定了多少次的按钮已被按下。大多数的飞机库,每个线程有很多内置的功能,如地图,过滤器,扫描等。当你调用这些函数之一,例如clickStream.map(F),它返回基于父一个新的流。该功能不修改父流。这就是所谓的不可改变并且是反应性的方法的一个组成部分,使我们能够调用函数的链,如clickStream.map(F).scan(克):


clickStream: ---c----c--c----c------c-->
vvv map(c 它正在成为 1) vvv
---1----1--1----1------1-->
vvvvvvvvv scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5-->


地图功能(F)根据您的实现函数f替换每个接收到的数值。在这种情况下,map函数产生的每次点击后的值“1”。扫描函数(G)的所有先前的聚合值,产生一个值x =克(积累,电流),其中g在这种情况下 - 这是简单地增加的函数。最终逆流引发的总点击数。


为了帮助您了解这种被动方式的力量,让我们假设要实现事件流“双击”。为了使这项工作更有趣的新线程必须采取多种按下双。想象一下,你将如何实现在命令行式风格这个任务。这将需要持有状态的几个变量,以及使用的插槽。


喷射是很简单的方法。所有逻辑可以用上述四行的代码来实现。但是,我们不要纠缠于代码。最好的方式来学习理解和设计流程,无论你是初学者还是专家 - 是一个示意图:




灰色矩形 - 是变换一个流至另一个功能。首先,我们收集在列表中点击。如果250毫秒已经通过未经按钮的单次压榨 - 我们应用功能图谱()在每个列表,以计算它的长度。之后,我们筛选使用长度为1过滤功能(X> = 2)名单。因此,在三个步骤中,我们得到的结果 - 的多次点击的事件的流程。我们可以订阅它,并根据需要使用。


这个例子说明了这乍一看来实现一个相当艰巨的任务,如果我们用一个被动的做法简单。


你需要无功规划是什么
被动的方法提高了抽象的级别在你的代码,你可以专注于它定义的业务逻辑事件的关系,而不是不断地保持与很多执行细节的代码。在喷射编程代码很可能是短。
其优点是在如今的网络和移动应用与种类繁多的UI事件的工作更加明显。 10年前,与网页的所有交互降低到服务器上发送大量形式和执行一个简单的客户端呈现。现在,应用程序更复杂的:在一个领域的变化会导致自动保存在服务器上的数据,关于新的“喜欢”的信息必须去其他连接用户,等

无编程非常适合于大事件的处理。
我们开始思考喷气风格
下面的示例使用JavaScript和RxJS,但接收的库可用于许多其他语言和平台(.NET,Java和Scala中,Clojure的,JavaScript中,红宝石,Python和C ++,Objective-C的/可可的,Groovy等。 )。在我们的网站有Python的手册中的使用图书馆和RxSwift ReactiveX的。
我们销售的一个小部件“谁订阅”
在Twitter上,有一个提供给你可以订阅其他帐户一个小部件:


我们打算实现其基本功能:


该API和三个账户的结论的负荷;
通过点击按钮“更新”其他三个账户的输出;
点击«点¯x»按钮旁边的账户 - 从部件中删除并显示其他账户;
显示头像,并链接到每个三行的帐户。
而是,闭合以未经授权的用户Twitter的帐户,我们将使用Github的API和从那里的帐户。该API Github上的链接来获得用户的列表可在官方文档中找到。您也可以看看这个例子中完成的代码。

请求和响应
如何解决这个问题,在接收式的解决方案?我们必须以事实(几乎)什么都可以流开始。我们认识到的第一件事情,是一个“从API和三个账户的取款下载”。没有什么不寻常,你只需要(1)提出请求,(2)接收响应,(3),以显示答案。提交请求,作为流。
在初始化期间,我们必须使只有一个请求,因此,如果我们模拟它作为数据流,它会被布置只有一个值的。在未来,我们会做很多的请求,但目前我们只需要一个。

--a------|->

'https://api.github.com/users'


凡 - 是一个字符串'https://api.github.com/users“


时,有一个请求,它告诉我们两件事情:当请求被执行 - 时间事件的产生,并且其中我们提出请求 - 由事件产生的值,包含URL的字符串。


创建包含一个值的流,很简单的家庭与图书馆接收*:



var requestStream = Rx.Observable.just('https://api.github.com/users');



我们已经写了 - 它只是包含字符串,什么都不做甲流,所以我们有类似的东西,怎么办,我们需要让他采取行动。这是通过订阅该流完成:


requestStream.subscribe(function(requestUrl) {
// выполняем запрос
jQuery.getJSON(requestUrl, function(responseData) {
// ...
});
}



注意,我们使用jQuery库的Ajax的回调(回调)来管理异步查询操作。如果你知道什么小回调,请阅读我们的JS中的异步编程的发展文章。 “别急,接收还与异步数据流。难道是到包含数据的请求流的响应会来晚些时候?“ - 你可能会问。那么,在概念层次上,所有的权利,让我们试着来实现:

requestStream.subscribe(function(requestUrl) {
// выполняем запрос
var responseStream = Rx.Observable.create(function (observer) {
jQuery.getJSON(requestUrl)
.done(function(response) { observer.onNext(response); })
.fail(function(jqXHR, status, error) { observer.onError(error); })
.always(function() { observer.onCompleted(); });
});

responseStream.subscribe(function(response) {
// делаем что-то с ответом
});
}

Rx.Observable.create()创建一个自定义数据流,每个用户通知有关事件(onNext())或错误(onerror的())。我们包裹Ajax的PROMIS到适当的回调。这是否意味着承诺 - 同样的事情,可观察到的?是的,我的意思。更多关于Promis'ah阅读我们的介绍性文章。



观察到的 - 这个承诺++。在RX可以转换观测承诺以一种非常简单的方法:


var stream = Rx.Observable.fromPromise(promise);

观测承诺和那之间的唯一区别是不符合观测承诺/ A +兼容。承诺 - 是,事实上,观测一个产生价值。流在接收延长无极,允许返回一组值。


回到我们的例子:你可以看到我们所说的订阅()函数两次 - 在另一个内。也使responseStream取决于requestStream。如上所述,接收有简单的机制来改造和创造另一个新的流,因此,我们应该利用这一点。


其中的一个功能,与你已经很熟悉 - 图(F) - 需要从流中的每个值,其应用F()和流B.产生的值。如果我们应用此功能的请求和响应流程,我们可以转换响应PROMIS URL列表。

var responseMetastream = requestStream
.map(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});


然后,我们需要创建一个流流动,称为metapotok(metastream)。别担心,一切都非常简单。 Metapotok - 是在他们的流量被生成的每个值的流。你可以把它们想象为指针:每个生成的值 - 一个指向一个新的线程。在本例中,为每个查询的URL变换为指向包含PROMIS响应流。




“但是,为什么我们在溪流的回答结束了?” - 你可能会问。在这种情况下,可以转换的服务器响应metapotok的正常流动,其中每个值由flatmap()函数生成的JSON对象,而不是PROMIS。但是,尽管如此,metapotoki - 普遍在反应编程,并且特别地,它们被用于处理异步请求。

var responseStream = requestStream
.flatMap(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});



如所预期的,当我们随后将是请求的流程生成的某些事件,响应流将相应地作出反应,以它们:

requestStream:  --a-----b--c------------|->
responseStream: -----A--------B-----C---|->

(小写表示的请求时,顶端 - 的答案)


而现在,当我们有响应流,我们可以使所得到的数据:


responseStream.subscribe(function(response) {
// 渲染答案DOM
});


所有的代码全部:


var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseStream = requestStream
.flatMap(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});

responseStream.subscribe(function(response) {
// 渲染答案DOM
});


刷新按钮




应当指出的是,我们通过API获取用户列表,是由100个成员的。该API允许我们指定的偏移名单,但不是它的大小,只要我们只用3个对象,忽略其它。您将学习如何在以后缓存响应。


每个用户按下刷新按钮,查询应生成URL的流动,这样我们就可以获取新的数据。对于这个任务,我们需要做两件事情:实现在按下按钮的事件流和变更请求的流程,使其响应水龙头流事件。幸运的是,RxJS使我们能够变换JavaScript的观测普通事件。


var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');


让我们改变请求的流程,这样当你按下刷新按钮与偏移参数的随机列表来生成一个URL。


var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});

它看起来像我们打破东西。现在,该请求被触发之后,才会按下按钮。但是问题的条件下,我们必须作出请求和初始化。让我们来尝试解决我们的代码。


首先,我们创建了上述条件,不同的流:


var requestOnRefreshStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});

var startupRequestStream = Rx.Observable.just('https://api.github.com/users');

但现在怎么这两个流的事件连接成一个?这将帮助我们的功能合并()。这里是做什么的可视表示:


stream A: ---a--------e-----o----->
stream B: -----B---C-----D-------->
vvvvvvvvv merge vvvvvvvvv
---a-B---C--e--D--o----->


好了,现在一切都非常简单:


var requestOnRefreshStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});

var startupRequestStream = Rx.Observable.just('https://api.github.com/users');

var requestStream = Rx.Observable.merge(
requestOnRefreshStream, startupRequestStream
);

此外,还存在另一种更清洁的方式来实现,而不附加变量的目标:

var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
})
.merge(Rx.Observable.just('https://api.github.com/users'));


你甚至可以短!

var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
})
.startWith('https://api.github.com/users');

你有没有注意到,我们已经重复的网址是什么?让我们摆脱重复的,移动startWith()接近refreshClickStream,模拟应用过程中按:


var requestStream = refreshClickStream.startWith('startup click')
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});

建模与线程3建议



现在,随着装修一下,我们有一个问题:当你按下按钮当前三项建议不会消失。新的优惠,一旦出现服务器回来了,但为了使我们的UI响应看,我们必须立即清理目前的建议。


refreshClickStream.subscribe(function() {
// очищаем текущие рекомендации
});
现在我们有两个用户影响DOM元素(其他订购了responseStream)与此相对应“分而治之”的原则。你还记得的口头禅反应的方法呢?提醒: