从命令式到响应式(四)

字节觅云使
• 阅读 1241

上期介绍过了rxjs中的三大件,Observable,subscription,subject,但是在开发过程我们最常接触到的东西非操作符莫属。比如上期代码中曾出现过的from就是一个操作符。rxjs中的操作符大致上可以分为几类,创建类,组合类,转换类,过滤类,条件类,聚合类,错误处理类,多播类及工具类,其中前四类是数据处理时使用频率非常高的,在本节及下一节中将介绍其中一些使用频率非常高的操作符。rxjs一共提供了120个左右操作符,合理的使用这些操作符会使我们获取愉快的编码体验。

如何学习操作符

首先需要分清的是操作符是属于实例方法还是静态方法,实例方法的实例当然指的是Observable类的实例,通常情况会在数据转换的过程中使用;而静态方法当然指的是Observable类的静态方法,只能通过Observable类来调用,大部分创建类型的操作符都是静态方法,在rxjs5中区别非常明显,例如:

import 'rxjs/Observable';
import 'rxjs/add/operator/map';

// 这里的interval 是静态方法,而map就是实例方法。
Observable.interval(1000).map(num => num * num);

在rxjs6中还可能这样写:

import { interval } from 'rxjs/observable/interval';
import { map } from 'rxjs/operators/map';

// 引入的位置是不一样的。
interval(1000)
    .pipe(
        map(num => num * num)
    );

最直观的描述操作符的行为的方式就是弹珠图,在官网上重要的操作符基本上都给出了相应的弹珠图。从现在开始为了表达的简洁,我们把可观测序列称之为流,弹珠图各部分的含义如下:

// 这条从左到右的横线代表随时间的推移,输入流的执行过程。
// 横线上的值代表从流上发射出的值
// 横线尾部的竖线代表complete通知执行的时间点,表示这条流已经成功的执行完成。
----------4------6--------------a-------8-------------|---->

            multipleByTen // 使用的操作符

// 这条从左到右的横线代表经过操作符转换后的输出流。
// 横线尾部的X代表在这个时间点上流发生了错误,至此之后不应该再有 Next 通知或 Complete 通知从流上发出。
---------40-----60--------------X--------------------------->

前面说过操作符会把我们的数据进行转换,在响应式编程中,我们应该尽量保持数据在流中进行转换,而不是时刻想着去subscribe一条流,取出数据,再转换数据。尤其在angular中,能不手动的subscribe的流,一定要力求不主动订阅,最典型的就是页面上需要显示的数据,我们完全可以交给async管道来进行订阅。OK,啰嗦了一大堆,下面主角登场。

创建类操作符

  1. from

静态方法

将数组、类数组对象、promise、部署了遍历器接口的对象或类 Observable 对象转换成Observable,它
几乎可以将任何东西都转换成流,并且将原数据上的值依次推送到流上,字符串被当成由字母组成的数组进行转换。

            from([1,2,3])

    1--------------2--------------3|

示例

from([1,2,3,4,5]).subscribe(v => console.log(v));

function* generatorDoubles(seed) {
    var i = seed;

    while(true) {
        yield i;

        i = i*2
    }
}

const iterator = generatorDoubles(3);
from(iterator).take(5).subscribe(v => console.log(v));
  1. of

静态方法

创建一个流,把传入此函数的参数从左到右依次推送到流上,然后发出结束通知。

            of(1,2,3);

    1-------------2--------------3|

示例

of(10,20,30).subscribe(v => console.log(v));
  1. timer

静态方法

创建一个输出流,在指定的延迟时间到达后开始发射值,在指定的间隔时间到达后发射递增过的值。类似于interval,但是这个操作符允许指定流开始发射值的时间,

            timer(3000, 1000);

    ------0--1--2--3--4--5--------->

第一个参数代表等待时间,第二个参数代表时间间隔,这些值是一些数字常量。等待时间可以是一个毫秒数,也可以是一个日期对象。如果没有指定时间周期,输出流上只会发射0,反之,它会发出一个无限的数列。

示例

Rx.Observable.timer(3000, 1000)
    .subscribe(v => console.log(v));

Rx.Observable.timer(5000)
    .subscribe(v => console.log(v));

过滤类操作符

  1. filter

实例方法

创建一个流,它的值是使用判定函数对输入流发出的值进行过滤后的值。

    --0--1--2--3--4--5----|-->

            filter(v => v % 2 === 0);

    --0-----2-----4-------|>

和数组的filter方法行为一样,从输入流上获取值,使用判定函数对值进行过滤,只有符合过滤条件的值才会在输出流上发出。

返回值 Observable 通过判定函数检测的值组成的流。

示例

from([1,2,3,4,5,6])
    .filter(num => num %2 === 0)
    .subscribe(v => console.log(v));
  1. first

实例方法

发送输入流上的第一个值,或者第一个符合某些条件的值。

    ---------a-------b------c---------d-->

            first

    ---------a|

在不传入任何参数时,这个操作符仅发出输入流上的第一个值,然后立即发出结束通知。如果传入一个判定函数,则发出第一个通过判定函数检测的值。它还可以接受一个结果控制函数来转化输出的值,或一个在输入流没有发出符合条件的值情况下使用的默认值。如果没有提供默认值,并且在输入流上也没有找到符合条件的值时,输出流将会抛出错误。

返回值 Observable 第一个符合条件的值。

异常 EmptyError 在结束通知发出前如果没有发出过有效值,将会发送一个错误通知给观察者。

示例

from([2,3,4])
    .first()
    .subscribe(v => console.log(v));

from([2,3,4])
    .first(num => num === 5)
    .subscribe(v => console.log(v)); // EmptyError;
  1. skip

实例方法

返回一个跳过指定数量的值的流。

---a---b---c---d---e---|->

            skip(3);

---------------d---e---|>

返回值 Observable 跳过了一定数量值的流。

示例

from([1,2,3,4,5,6])
    .skip(3)
    .subscribe(v => console.log(v));
  1. take

实例方法

从第一个值开始发出指定数量的值,然后发出结束通知。

    ---a------b------c------d-----e---|-->

            take(3);

    ---a------b------c|

输出流仅仅发出了输入流上从第一个值开始的n个值。如果输入流上值的个数小于n,那么所有的值都会被发出。值发射完成后,不管输入流有没有发出结束通知,输出流都会立即发出结束通知。

返回值 Observable 发出输入流上从第一个值开始的n个值,或者输入流发出值的个数小于n时发出所有的值的流。

异常 ArgumentOutOfRangeError 在给此操作符传入负数时给观察者发出的错误。

示例

interval(1000)
    .take(5)
    .subscribe(v => console.log(v));
  1. takeUntil

实例方法

在通知流发出通知之前,持续发射输入流上的值。在通知流发出值之前,输出流完全就是输入流的镜像。此操作符会一直监视传入的通知流,如果通知流发出了值或结束通知,输出流就会停止发射输入流上的值,并发出完成通知。

返回值 Observable 持续发出输入流上的值,直到通知流上发出值为止。

示例

Rx.Observable.interval(1000)
    .takeUntil(Rx.Observable.fromEvent(document, 'click'))
    .subscribe(v => console.log(v));

学习操作符时,我们还要关注的一点是,这个操作符是否会发出结束通知,一方面订阅发出结束通知的流时,在库的底层会帮助我们释放资源可以省去手动取消订阅,比如 angular 中 http 服务上的方法,另一方面结束通知可能会影响接下来你使用的操作符,典型的如reduce 和 scan,在一个不发出结束通知的流上使用reduce时你将永远不会得到结果。

从命令式到响应式(四)

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
3年前
java常用类(2)
三、时间处理相关类Date类:计算机世界把1970年1月1号定为基准时间,每个度量单位是毫秒(1秒的千分之一),用long类型的变量表示时间。Date分配Date对象并初始化对象,以表示自从标准基准时间(称为“历元”(epoch),即1970年1月1日08:00:00GMT)以来的指定毫秒数。示例:packagecn.tanjian
Wesley13 Wesley13
3年前
java8新特性
Stream将List转换为Map,使用Collectors.toMap方法进行转换背景:User类,类中分别有id,name,age三个属性。List集合,userList,存储User对象1、指定keyvalue,value是对象中的某个属性值。 Map<Integer,StringuserMap1userList.str
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Stella981 Stella981
3年前
Mybatis别名的配置使用
之前,我们在sql映射xml文件中的引用实体类时,需要写上实体类的全类名(包名类名),如下<! 创建用户(Create)   <insert id"addUser" parameterType"me.gacl.domain.User"    insert into users(name,