资讯专栏INFORMATION COLUMN

探索 RxJS - Core Concept

Neilyo / 3337人阅读

摘要:但不同的是,在的遍历调用过程中,如果一个事件还没有触发完毕获取到返回值,就触发了下一个事件,则将忽略返回的值。这样,我们就可以避免异步的返回值因为返回较慢,反而覆盖了之后异步的返回值。

Steam in ReactiveX

ReactiveX,又称 Reactive Extensions(响应式扩展),其中的 X 代表各种语言。因为它实质上只是一个事件流处理库,不需要什么其他依赖。

讲一讲 ReactiveX 中的 “流”:

我的理解是,Rx 通过 “流” 的概念,将事件串联成一个个事件流,各个事件流之间还可进行 "并联" 的作用。当某个流上的事件被调用时,就可以触发我们设定好的监听回调。

那么什么样的事件可以成为流呢?答案是任何事件。无论是异步非阻塞事件(setTimeOut、网络请求等),还是同步可阻塞事件(点击事件、对迭代器的遍历等),一切都是流。此时不得不祭出一张神棍图:

事件的串联是流。比方说,用户对一个按钮进行了猛烈的点击,所有的点击事件就是一个流;再或者,并发多个网络请求,它们也是一个流。而 Rx 的主要作用,就是为流的处理提供了一整套的解决方案:将不同的流进行组合,或者监听事件的触发及时给予响应等等。

Quick Intro

RxJS 的作者曾在 The introduction to Reactive Programming you"ve been missing 一文中详细讲解了 RxJS 的初步使用和流的概念,我们先仅看文中的一幅图来理解 Rx 的概念:

这是一个多次点击事件形成的事件流,在从左往右的时间线上有很多个点击事件,而每个点击事件的时间间隔各不相同。通过 Rx 我们可以对流上的各个事件进行筛选,并获取到在某一段时间内的连续点击次数。

作者自己吐槽过那篇文章实在太长,所以又有了后来的这篇 2 minute introduction to Rx 文章。在文章中有如下解释:

我们在页面上的点击事件就可以组成流。比如一个记录每次点击时坐标的流。随机点击页面多次之后,可能会产生这样一个流:

而我们可以通过 RxJS 中的方法对这个流内的各个事件进行筛选,比如选出横坐标 x < 250 的点击:

filter( (event) -> event.x < 250 )

也就是说,我们可以像对待 JavaScript 中可遍历对象一样,对流上的各个事件进行遍历,选出符合条件的事件。这就是 Rx 的魅力所在。

主要运用场景

既然 Rx 是为了流而生的,那么最佳运用场景当然是面对一系列较复杂的事件流时了。

含有异步请求和事件触发的混合流

比如,用户在一个 input 内输入文字。每次keyup的时候就会根据输入内容,请求 Wikipedia 的 API 进行搜索:

var input = document.getElementById("input");
// 通过 fromEvent 以及 input keyup 事件创建一个流
var dictionarySuggest = Rx.Observable.fromEvent(input, "keyup")
  // 获取到每次 keyup 时的input value,并通过 filter 保证其合法性
  .map(function () { return input.value; })
  .filter(function (text) { return !!text; })
  .distinctUntilChanged()
  .debounce(250)
  // searchWikipedia 为异步请求方法
  .flatMapLatest(searchWikipedia)
  .subscribe(
      // onNext
    function (results) {
      list = [];
      list.concat(results.map(createItem));
    },
    // onError
    function (err) {
      logError(err);
    }
  );

我们创建了一个流来处理从用户keyup,到searchWikipedia,再到处理网络请求结果这一系列事件,并且在其中对事件进行了筛选判断:

filter 剔除掉不合法的值

distinctUntilChanged 当用户按下例如 左、右 这种按钮时,不会改变 input 的值,但也会触发keyup事件。这种时候就完全没有必要重复发送异步请求了。distinctUntilChanged会剔除流中有着相同的值的元素

debounce 在过了一段指定的时间还没触发事件时才触发下一个事件。也就是说,在打字过程中,如果用户在指定事件间隔(250ms)内没有再打字,则触发下一个事件(searchWikipedia);否则我们认为用户在连续打字,所以不会频繁的发送网络请求

flatMapLatest

首先,它是一个flatMap方法。它用一个指定的函数(searchWikipedia)对原始流中的每一项数据执行变换操作,并返回一个ObservableflatMap将所有的返回值组成一个新的流。

其次,flatMapLatest拥有flatMap的全部特性。但不同的是,在flatMapLatest的遍历调用过程中,如果一个事件 A 还没有触发完毕获取到返回值,就触发了下一个事件 B,则将忽略 A 返回的值。这样,我们就可以避免 A 异步的返回值因为返回较慢,反而覆盖了之后 B 异步的返回值。用图解释如下:

subscribe 创建对流的监听,并提供了成功和失败的回调

而在传统的编写方法里,我们可能会创建 input 的keyup监听事件,并缓存上一次的值;每次keyup时,要判断当前值是否合法,并且与上一次的值不一样。除此以外,还要创建一个定时器,每隔一段时间就用合法的值去请求searchWikipedia方法 --- 即便这样,也无法保证不在用户连续打字时发送请求。

可以看到,在我们把事件串成流并进行处理之后,要比传统的编写方式方便很多。

处理一系列的异步请求队列

假设我们要读取一个 4GB 的大文件,将其加密后写入到一个新文件里。直接将整个文件读到内存里再加密、写入肯定是不行的,反之,我们依赖 RxJS 的流,创建多个读取、加密、写入事件,形成三个流出来:

文件读取流:每次调用方法时异步读取 64k 的文件

加密流:对读取的文件进行加密

写入流:将加密好的内容异步写入新文件

最后对整个observable进行监听

var fs = require("fs");
var Rx = require("rx");

// Read/write from stream implementation
function readAsync(fd, chunkSize) { /* impl */ }
function appendAsync(fd, buffer) { /* impl */ }
function encrypt(buffer) { /* impl */}

// 打开一个 4GB 的文件,每次只读取 64k
var inFile = fs.openSync("4GBfile.txt", "r+");
var outFile = fs.openSync("Encrypted.txt", "w+");

readAsync(inFile, 2 << 15)
  .map(encrypt)
  .flatMap(function (data) {
    return appendAsync(outFile, data);
  })
  .subscribe(
      // onNext
    function () { },
    // onError
    function (err) {
      console.log("An error occurred while encrypting the file: %s", err.message);
      fs.closeSync(inFile);
      fs.closeSync(outFile);
    },
    // onCompleted
    function () {
      console.log("Successfully encrypted the file.");
      fs.closeSync(inFile);
      fs.closeSync(outFile);
    }
  );

由此可以看出,在应对较复杂的事件流或者处理多个异步事件的时候,使用 RxJS 会有一定优势;但如果复杂度没有这么高的时候则没有太大的使用必要。

目前为止,本文基本介绍了 RxJS 的核心概念 --- 针对事件流的管理与掌控。

在下一篇文章里,我们将会利用 RxJS,完成一个简单的 github 应用。戳:探索 RxJS - 做一个 github 小应用可查看文章,rxjs-example查看案例源码。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/80994.html

相关文章

  • Angular2中拦截器Intercept探索之路

    摘要:初衷之前看到正式发布了,过去看了下,感觉不错,于是入坑。不过思路还是可以借鉴的。尝试以下第一篇链接第二篇链接第三篇里写法过时了按照上述代码,写法与不同,不知道怎么改。 初衷 之前看到angular2正式发布了,过去看了下,感觉不错,于是入坑。使用过程中想写一个像angular1那样的拦截器,一路坎坷啊 Angular1中的拦截器 .factory(HttpRequestIntercep...

    instein 评论0 收藏0
  • 探索 RxJS - 做一个 github 小应用

    摘要:更加优雅的风格按照上面的教程,我们在中获取到了数据发送异步请求并拿到了最新一次的返回值。之后,再通过,在监听的回调中将返回值拼接成并插入。 本文是一篇 RxJS 实战教程,利用 RxJS 和 github API 来一步步做一个 github 小应用。因此,文章的重点是解释 RxJS 的使用,而涉及的 ES6语法、webpack 等知识点不予讲解。 本例的所有代码在 github 仓库...

    Pikachu 评论0 收藏0
  • angular模块库开发实例

    摘要:模块库开发实例随着前端框架的诞生,也会随之出现一些组件库,方便日常业务开发。在浏览器中,渲染是将模型映射到视图的过程。然而视图可以是页面中的段落表单按钮等其他元素,这些页面元素内部使用来表示。 angular模块库开发实例 随着前端框架的诞生,也会随之出现一些组件库,方便日常业务开发。今天就聊聊angular4组件库开发流程。 下图是button组件的基础文件。 showImg(htt...

    JerryZou 评论0 收藏0
  • angular入门

    摘要:学习入门第一课步骤创建应用程序的项目文件夹和定义包的依赖关系和特殊项目设置文件创建文件编译 angular2 学习入门第一课 步骤 Install Node.js 创建应用程序的项目文件夹和定义包的依赖关系和特殊项目设置 Create the app’s Angular root component Add main.ts, identifying the root componen...

    Yangder 评论0 收藏0
  • angular入门

    摘要:学习入门第一课步骤创建应用程序的项目文件夹和定义包的依赖关系和特殊项目设置文件创建文件编译 angular2 学习入门第一课 步骤 Install Node.js 创建应用程序的项目文件夹和定义包的依赖关系和特殊项目设置 Create the app’s Angular root component Add main.ts, identifying the root componen...

    Gu_Yan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<