使用 RxJS 实现 JavaScript 的 Reactive 编程 已翻译 100%

oschina 投递于 2016/02/24 16:07 (共 24 段, 翻译完成于 05-09)
阅读 17915
收藏 75
5
加载中

简介

作为有经验的JavaScript开发者,我们会在代码中采用一定程度的异步代码。我们不断地处理用户的输入请求,也从远程获取数据,或者同时运行耗时的计算任务,所有这些都不能让浏览器崩溃。可以说,这些都不是琐碎的任务,它是确切的需求,我们学着去避开同步计算,让模型的时间延时成为问题的关键。对于简单的应用程序,直接使用JavaScript的主事件系统,甚至使用jQuery库帮助也很常见。然而,还没有适当的模式来扩展的简单代码,解决这些异步问题,满足更丰富的应用特性,满足现代web用户的需求,这些仍然是困难的。我们越来越发现我们的应用代码正变得复杂,难以维护,难以测试。问题的本质是异步计算本身就是难以管理的,而RxJS可以解决这个问题。


溪边九节
溪边九节
翻译于 2016/03/09 19:59
3

RxJS解决的问题

任何应用最重要的一个目标之一就是在所有时刻保持响应。这意味着对于一个应用来说当它在处理用户输入或者凭借AJAX从服务器接受一些额外的数据时停止是一件不可接受的事情。通常来说,主要的问题是IO(输入/输出)运行(从磁盘或者网络读取)比CPU执行指令慢太多。这同时实用于客户端和服务器端。让我门来看看客户端。在JavaScript中,解决问题的方案始终是充分利用浏览器的多重连接并且用回调函数来大量产生一个独立的用来照顾一些长期运行的处理。这是一种反转控制控制形式,因为程序的控制不是被你操纵的(因为你不能预知某一处理什么时候会完成),而是在运行时间的责任下交还给你的。虽然对于小应用程序非常有用,但回调的使用使内容丰富的大型应用变得凌乱,它需要同时处理的数据来自用户以及远程HTTP的调用。我们都有过这样的经历:一旦你需要多块数据时你就陷入了流行的”末日金字塔“或者回调地狱

makeHttpCall('/items', 
   items => {
      for (itemId of items) {
         makeHttpCall(`/items/${itemId}/info`,
           itemInfo => {        
              makeHttpCall(`/items/${itemInfo.pic}`,
                img => {
                    showImg(img);
              });   
           });
      }
});

beginUiRendering();



植瑞
植瑞
翻译于 2016/03/11 12:31
2

这段代码有很多的问题。 其中之一就是风格。当你在这些嵌套的回调函数中添加越来越多的逻辑,这段代码就会变得很复杂很难理解。因为循环还产生了一个更加细微的问题。for循环是同步的控制流语句,这并不能很好的配合异步调用,因为会有延迟,这可能会产生很奇怪的bug。

这个问题一直都是JavaScript开发者的大麻烦,所以JavaScript在SE6中引入了Promises。 Promises帮助开发者解决这些类似的问题,它提供了一个非常流畅的接口来捕获时间并且提供一个回调方法then()。上面的代码就变成了:

makeHttpCall('/items')
    .then(itemId => makeHttpCall(`/items/${itemId}/info`))
    .then(itemInfo => makeHttpCall(`/items/${itemInfo}.pic}`))
    .then(showImg);

这毫无疑问是一个进步。理解这段代码的难度显著下降。然而,尽管Promises在处理这种单值(或单个错误)时非常高效,它有也一些局限性。Promimses在处理用户连续输入的数据流时效率怎么样呢? 这时Promises处理起来也并不高效,因为它没有事件的删除、分配、重试等等的语法定义。接下来开始讲解RxJS。

暖冰
暖冰
翻译于 2016/04/02 10:43
2

RxJS 初探

RxJS是一个解决异步问题的JS开发库.它起源于 Reactive Extensions 项目,它带来了观察者模式和函数式编程的相结合的最佳实践。 观察者模式是一个被实践证明的模式,基于生产者(事件的创建者)和消费者(事件的监听者)的逻辑分离关系.

况且函数式编程方式的引入,如说明性编程,不可变数据结构,链式方法调用会使你极大的简化代码量。(和回调代码方式说再见吧)。

若想仔细了解函数式编程,请访问这里(the Functional Programming in JavaScript RefCard )。

如果你熟悉了函数式编程,请把RxJS理解为异步化的Underscore.js。

RxJS 引入了一个重要的数据类型——流(stream)。

G海涛
G海涛
翻译于 2016/04/28 10:51
1

理解流( Streams)

流(Streams)无非是随时间流逝的一系列事件。流(Streams)可以用来处理任何类型的事件,如:鼠标点击,键盘按下,网络位数据,等等。你可以把流作为变量,它有能力从数据角度对发生的改变做出反应

变量和流都是动态的,但表现有些不同;为了理解它,让我们看一个简单的例子。考虑以下简单的算术运算:

var a = 2;
var b = 4;
var c = a + b;
console.log(c); //-> 6

a = 10;  // reassign a
console.log(c); //-> still 6
溪边九节
溪边九节
翻译于 2016/03/03 20:44
3

尽管变量a变为了10,但这是一种通过设计方式保证所依赖的变量不变。这就是最大的不同。事件引发的改变总是从事件源(生产者)
向下传递到所有事件监听方(消费者)。假如说把变量看成流,下面就会这样:

var A$ = 2;
var B$ = 4;
var C$ = A$ + B$;
console.log(C$); //-> 6

A$ = 10;  
console.log(C$); //->  16

这样你看到了,流的方式重新定义的变量值的动态行为。(作为习惯,我喜欢使用$符号在流变量命名里)
换句话说,C$是把两个流变量A$和B$进行合并操作。当一个新值被推进了A$,C$立刻响应式的变更为16。当前这只是一个
牵强的例子,距离真正的语法还很远。这个例子解释了变量在事件流中的变化过程。

现在让我们开始学习RxJS。


G海涛
G海涛
翻译于 2016/04/28 11:29
1

可观察数据类型

或许RxJS库最重要的部分是可观察数据类型的定义。这种类型被用于包装一个数据片段(按钮事件,键盘事件,鼠标事件,数字,字符串或者队列),这样它就有了流式数据类型的优点。最简单的观察对象是这种单变量形式,例如:

var streamA$ = Rx.Observable.of(2);

我们重新使用上面的例子,这次是真正的RxJS语法。这回使用了新的API,我要详细的讲一下:

const streamA$ = Rx.Observable.of(2);
const streamB$ = Rx.Observable.of(4);
const streamC$ = Rx.Observable.concat(streamA$, streamB$)
  .reduce((x, y) => x  + y);

streamC$.subscribe(console.log); //prints 6

运行这个例子输出值为6。不像之前的伪代码,在变量被定义后实际上不能对流对象重新赋值。如何必须那样做的话就要重新创建一个新的流变量,因为流变量是不可变的数据类型。既然是不可变的,通常我们可以安全的使用ES6规范里的不可变关键字const使代码更清晰明确。

G海涛
G海涛
翻译于 2016/04/28 12:17
1

为了给streamA$推一系列新值,你必须改变streamA$定义的方式:

const streamA$ = Rx.Observable.of(2, 10)
...
streamC$.subscribe(console.log); //prints 16

现在订阅streamC$将会得到值16。就像我之前提到的,流只是一个在时间轴上的事件传输序列。以下是可视化图例。

Simple stream

创建可观察序列对象

很多不同的方法都可以创建可观察序列对象。这里是一些普通使用的例子:

方法
说明
of(arg) 把参数转换成可观察序列对象
from(iterable) 把可迭代的队列参数转换成可观察序列对象
fromPromise(promise) 把promise对象参数转换成可观察序列对象
fromEvent(element, eventName) 通过增加一个事件监听器用于监听匹配的Dom元素,jQuery元素,Zepto元素,Angular元素,Ember.js元素或者EventEmitter等,
来创建可观察序列对象

流式编程的另一个不同点是触发机制。可观察序列类型对象是后触发的(lazy data types),就是说当有订阅者订阅的时候什么也不执行(这种方式不会有事件发出来)。它的订阅机制是被观察者(Observer)触发的。

G海涛
G海涛
翻译于 2016/04/28 13:17
1

观察者(Observer)

观察者代表模型的消费者一端。它负责被可观察序列对象发送过来的值进行处理和反馈。观察者的API简单,基于迭代者模式它定义了
next方法。当事件执行结果推向到可观察对象的时候,这个方法就会被调用。之前streamC$.subscribe(console.log) 这种简写的方式,其实就是背后创建了观察者对象(Observer)。创建的过程如下:

const observer = Rx.Observer.create(
    function next(val) {  
        console.log(val);
    },

    function error(err) { 
        ; // 事件异常情况执行
    },

    function complete() {
        ; // 事件完成后执行
    }

);

观察者也定义了处理异常的API,意味着会发出执行过程中的异常信号通知。所有的观察者对象里的方法都是可选择的,其实你只需要
订阅一下就可以(调用subscribe方法)。最普遍的方式是需要提供一下对应的业务方法映射到next方法里。这个方法里需要一个具体的业务逻辑,例如写文件,屏幕打印日志,追加到DOM里,不管怎么说需要你来完成的。

G海涛
G海涛
翻译于 2016/04/28 13:40
1

订阅

只要订阅一个Observable对象就会返回一个订阅对象,另外当完成后你可以使用unsubscribe方法释放掉对象流。这种释放机制真的很优美,它解决了原生JS在事件处理完成后正确释放资源的缺点。原生JS在这一块之前总是会出问题。

为了说明这一点,我创建一个Observable对象来监听所有的点击事件:

很明显这是一个无限触发的点击事件流(事件完成方法永远不会调用)。如果我要停止监听事件,我只需要简单的调用unsubscribe方法。这个方法也会清理和释放事件的句柄资源或者临时对象资源。

现在你知道了如何创建和销毁事件流,那么再看看怎么使用它来解决具体的问题吧。在我的模型里采用数字来说明这些API的使用,当然你可以把他们应用到任何的业务逻辑。

G海涛
G海涛
翻译于 2016/04/28 14:10
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(4)

程序师
程序师
有些段落完全是机器翻译的,太不严谨了。
j
jirodobiw
很赞
ios122
ios122
Promise.resole({then:(resolve, reject)=>{/*...*/}); // 其实,JS 自带天使光环的;
游客
游客
很赞
返回顶部
顶部