响应式编程(Reactive Programming)
- 响应式编程,即是使用异步数据流进行编程
在异步编程模式中,我们有两种获得任务执行结果的方式:
-
- 主动轮训,我们把它称为 Proactive 方式。
- 被动接收反馈,我们称为 Reactive 方式。
简单来说,在 Reactive 方式中,上一个任务的结果的反馈就是一个事件,这个事件的到来将会触发下一个任务的执行。这也就是 Reactive 的内涵。
RxJS初探
异步常见问题
异步API的多样性
我们除了要面对异步会遇到的各种问题外,还要烦恼很多不同的api写法,
- DOM Events
- XMLHttpRequest
- fetch
- WebSockets
- Server Send Events
- Service Worker
- Node Stream
- Timer
上面列的api都是异步的,但他们都有各自的api及写法。如果我们使用rxjs,就能用形式相近的api统一写法。
示例:监听点击事件,点击一次后不再监听
// 原生JS
var handler = (e) => {
console.log(e)
document.body.removeEventListener('click', handler) // 结束监听
}
// 注册监听
document.body.addEventListener('click', handler)
/* ============================================================================ */
// rxjs
Rx.Observable.fromEvent(document.body, 'click') // 注册监听
.take(1) // 只取一次
.subscribe(console.log)
RxJS基本介绍
- RxJS 是一个通过使用可观察序列(Observable sequences),组合【异步行为】和【基于事件的程序】的库。
- 函数式编程(Functional Programming) + 响应式编程(Reactive Programming),两个编程思想的结合。
- 观察者模式(push) + 迭代器模式(pull),两种设计模式的结合(渐进式获取数据的思想)。
RxJS中的主要概念
RxJS提供了:
核心概念: Observable(可观察对象)
- 在ES提案中,Observable是一个用来处理事件流的类,Observable的构造函数可以接受一个定义事件流的回调函数。
- 在RxJS中,Observable 是多个值的惰性推送集合,它可以发出多个值,并且可以被观察者订阅来接收这些值。
Observable的创建(基于Observer)
RxJS有很多的创建Observable的方法(通常我们使用所谓的创建操作符, 像 of、from、interval、等等)。
我们以最基础的create为例:
const observable = Rx.Observable.create(function (observer) {
observer.next(1);
observer.next(2);
observer.next(3);
try {
observer.next(4);
} catch(e) {
observer.error(e);
} finally {
setTimeout(() => {
observer.next(5);
observer.complete();
}, 3000);
}
});
在创建Observable对象时,我们为create方法传入了一个函数,该函数的参数是一个Oberver对象,在该函数内部,我们可以用同步或异步代码,编写Oberver对象的回调逻辑。
Observable的订阅及清理(基于Subscription)
- Observable对象创建时传入的函数,并不会立即执行,而是会等待一个Observer对象发起订阅时才执行。
- Observer对象通过将自己传入Observable对象的subscribe方法发起订阅,返回一个Subscription对象。
- 每次subscribe,Observable对象都会重新执行一次,类似于函数调用,每次执行有单独的上下文。
- 通过Subscription对象,我们可以在有限的时间内中止Observable对象的执行。
const observer = {
next: value => console.log(value), // next回调
error: err => {}, // error回调
complete: () => console.log('this is the end') // complete回调
};
const subscription = observable.subscribe(observer);
setTimeout(() => {
subscription.unsubcribe(); // 终止Observable的执行
}, 1000);
操作符(Operators)
创建相关(of, from, fromEvent, fromPromise, never, empty, throw, interval, timer)
const _source = Rx.Observable.create(function(observer) {
observer.next('A');
observer.next('B');
observer.next('C');
observer.complete();
});
// of
const source_of = Rx.Observable.of('A', 'B', 'C');
// from
const _fromObj =
['A', 'B', 'C']
|| 'ABC'
|| new Set(['A', 'B', 'C']);
const source_from = Rx.Observable.from(_fromObj);
// fromPromise
const _resolve = (str) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(str);
}, 100);
});
};
const source_fromPromise_0 = Rx.Observable.from(_resolve('This is fromPromise'));
const source_fromPromise = Rx.Observable.fromPromise(_resolve('This is fromPromise'));
// fromEvent
const source_fromEvent = Rx.Observable.fromEvent(document.body, 'click');
// fromEventPattern
const _ev = new CustomEvent(); // CustomEvent类需要同时具有 注册监听/移除监听 两种方法
const source_fromEventPattern = Rx.Observable.fromEventPattern(
(handler) => _ev.addListener(handler),
(handler) => _ev.removeListener(handler)
);
// empty, never, throw
const source_empty = Rx.Observable.empty(); // 创建一个空Observable,立即complete
const source_never = Rx.Observable.never(); // 创建一个一直存在的Observable,什么都不做
const source_throw = Rx.Observable.throw('err'); // 创建一个Observable,立即error
// interval, timer
// setInterval,next一个从0开始递增的整数
const source_interval = Rx.Observable.interval(1000);
// 等待1000ms,输出0,然后每隔5000ms输出一个递增的整数
const source_timer = Rx.Observable.timer(1000, 5000);
// 从Date对象的时间开始发送第一个值
const source_timer_date = Rx.Observable.timer(new Date(), 5000);
// 等待1000ms,输出1,同时complete
const source_timer_cut = Rx.Observable.timer(1000)
其它常用操作符(迭代器处理)
// map
const source = Rx.Observable.interval(1000);
const newest = source.map(x => x + 2);
// source: -----0-----1-----2-----3--...
// map(x => x + 1)
// newest: -----1-----2-----3-----4--...
/* ============================================================= */
// mapTo
const source = Rx.Observable.interval(1000);
const newest = source.mapTo(2);
// source: -----0-----1-----2-----3--...
// mapTo(2)
// newest: -----2-----2-----2-----2--...
/* ============================================================= */
// filter
const source = Rx.Observable.interval(1000);
const newest = source.filter(x => x % 2 === 0);
// source: -----0-----1-----2-----3-----4-...
// filter(x => x % 2 === 0)
// newest: -----0-----------2-----------4-...
/* ============================================================= */
// take
// first 和take(1)一样
const source = Rx.Observable.interval(1000);
const example = source.take(3);
// source : -----0-----1-----2-----3--..
// take(3)
// example: -----0-----1-----2|
/* ============================================================= */
// takeUntil
const source = Rx.Observable.interval(1000);
const click = Rx.Observable.fromEvent(document.body, 'click');
const example = source.takeUntil(click);
// source : -----0-----1-----2------3--
// click : ----------------------c----
// takeUntil(click)
// example: -----0-----1-----2----|
/* ============================================================= */
// concatAll
var click = Rx.Observable.fromEvent(document.body, 'click');
var source = click.map(e => Rx.Observable.of(1,2,3));
var example = source.concatAll();
// click : ------c------------c--------
// map(e => Rx.Observable.of(1,2,3))
// source : ------o------------o--------
// \ \
// (123)| (123)|
// concatAll()
// example: ------(123)--------(123)------------
/* ============================================================= */
// skip
const source = Rx.Observable.interval(1000);
const example = source.skip(3);
// source : ----0----1----2----3----4----5--....
// skip(3)
// example: -------------------3----4----5--...
/* ============================================================= */
// takeLast 等到整個 Observable 完成(complete)时输出
// last 和takeLast(1)等同
const source = Rx.Observable.interval(1000).take(6);
const example = source.takeLast(2);
// source : ----0----1----2----3----4----5|
// takeLast(2)
// example: ------------------------------(45)|
/* ============================================================= */
// concat
const source = Rx.Observable.interval(1000).take(3);
const source2 = Rx.Observable.of(3)
const source3 = Rx.Observable.of(4,5,6)
const example = source.concat(source2, source3);
const example1 = Rx.Observable.concat(source, source2, source3);
// source : ----0----1----2|
// source2: (3)|
// source3: (456)|
// concat()
// example: ----0----1----2(3456)|
/* ============================================================= */
// startWith
const source = Rx.Observable.interval(1000);
const example = source.startWith(0);
// source : ----0----1----2----3--...
// startWith(0)
// example: (0)----0----1----2----3--...
/* ============================================================= */
// merge
const source = Rx.Observable.interval(500).take(3);
const source2 = Rx.Observable.interval(300).take(6);
const example = source.merge(source2);
// source : ----0----1----2|
// source2: --0--1--2--3--4--5|
// merge()
// example: --0-01--21-3--(24)--5|
/* ============================================================= */
// combineLatest 在source和newest有新的next时,触发example的next,输出expamle函数计算所得
const source = Rx.Observable.interval(500).take(3);
const newest = Rx.Observable.interval(300).take(6);
const example = source.combineLatest(newest, (x, y) => x + y);
// source : ----0----1----2|
// newest : --0--1--2--3--4--5|
// combineLatest(newest, (x, y) => x + y);
// example: ----01--23-4--(56)--7|
/* ============================================================= */
// zip 当source和newest都已送出第n个元素时,输出example函数计算所得
// zip 会缓存需要等待的数据,所以需要考虑内存问题
const source = Rx.Observable.interval(500).take(3);
const newest = Rx.Observable.interval(300).take(6);
const example = source.zip(newest, (x, y) => x + y);
// source : ----0----1----2|
// newest : --0--1--2--3--4--5|
// zip(newest, (x, y) => x + y)
// example: ----0----2----4|
/* ============================================================= */
// withLatestFrom
const main = Rx.Observable.from('hello')
.zip(Rx.Observable.interval(500), (x, y) => x);
const some = Rx.Observable.from([0,1,0,0,0,1])
.zip(Rx.Observable.interval(300), (x, y) => x);
const example = main.withLatestFrom(some, (x, y) => {
return y === 1 ? x.toUpperCase() : x;
});
// main : ----h----e----l----l----o|
// some : --0--1--0--0--0--1|
// withLatestFrom(some, (x, y) => y === 1 ? x.toUpperCase() : x);
// example: ----h----e----l----L----O|
简易的鼠标拖拽监听Demo
const dragDOM = document.getElementById('drag');
const body = document.body;
const mouseDown = Rx.Observable.fromEvent(dragDOM, 'mousedown');
const mouseUp = Rx.Observable.fromEvent(body, 'mouseup');
const mouseMove = Rx.Observable.fromEvent(body, 'mousemove');
mouseDown
.map(event => mouseMove.takeUntil(mouseUp)) // mouseDown时转换成mouseMove监听,并在mouseUp时结束
.concatAll() //拍平成一维
.map(event => ({ x: event.clientX, y: event.clientY })) // 输出事件的鼠标x,y坐标
.subscribe(pos => {
dragDOM.style.left = pos.x + 'px';
dragDOM.style.top = pos.y + 'px';
})
Operators的执行特点
附属类型:Subject
Subject类型详解
- 对于一般的Observable,每次订阅时都是一次全新的执行,每个订阅间都是完全独立的。而对于某些事件,我们希望多个订阅之间可以共享事件或数据流,这时我们就需要Subject的参与。
- Subject继承自Observable,实际作为订阅的中间人,Subject既是Observable,又是Observer。
- Subject订阅sourceObservable。
- Subject在内部维护一个Observer列表,每当sourceObservable有元素push时,遍历Observer列表并主动push元素。
- Subject对象的subscribe方法,实际上就是向Observer列表中加入新的Observer对象。
- Subject对象的connect方法,实际上才对应着Observable的subscribe,即决定执行时机。
Subject对象的分类
-
Subject
- 基础的Subject对象,实现了多播,并不维护数据或事件的状态。
-
BehaviorSubject
- 有一个“当前值”的概念。它保存了发送给消费者的最新值,当有新的观察者订阅时,会立即从BehaviorSubject那接收到“当前值”,在定义一个BehaviorSubject时需要有初始值。
-
ReplaySubject
- 类似于BehaviorSubject,可以发送旧值给新的订阅者,但是不仅是‘当前值’,还可以是之前的旧值。在定义时可以传入需要缓存最后几个值,还可以指定 window time (以毫秒为单位)来确定多久之前的值可以记录,取交集。
-
AsyncSubject
- 只有当Observable执行完成时(complete),它才会将执行的最后一个值发送给观察者,和last操作符类似。
关于Subject的操作符
// multicast
// 挂载subject,返回一个可connect的observable
// publish是multicast的简化写法
const source = Rx.Observable.interval(1000).take(3)
.multicast(new Rx.Subject());
const observerA = {
next: value => console.log('A next: ' + value),
error: error => console.log('A error: ' + error),
complete: () => console.log('A complete!')
}
const observerB = {
next: value => console.log('B next: ' + value),
error: error => console.log('B error: ' + error),
complete: () => console.log('B complete!')
}
source.subscribe(observerA); // subject.subscribe(observerA)
source.connect(); // source.subscribe(subject)
setTimeout(() => {
source.subscribe(observerB); // subject.subscribe(observerB)
}, 1000);
// refCount
// 必须搭配multicast,建立一个只要有订阅就会自动connect的observable
// publish + refCount 可以简写为share
const source = Rx.Observable.interval(1000)
.do(x => console.log('send: ' + x))
.multicast(new Rx.Subject())
.refCount();
const subscriptionA = source.subscribe(observerA);
// 订阅数 0 => 1
let subscriptionB;
setTimeout(() => {
subscriptionB = source.subscribe(observerB);
// 订阅数 0 => 2
}, 1000);
附属类型:Scheduler
Scheduler是什么
-
调度器控制着何时启动Subscription和何时发送通知。它由三部分组成:
- 调度器是一种数据结构。 它知道如何根据优先级或其他标准来存储任务和将任务进行排序。
- 调度器可以充当执行上下文。 它表示在何时何地执行任务(立即执行,回调函数机制(setTimeout,process.nextTick等),动画帧)。
- 调度器有一个(虚拟的)时钟。 调度器功能通过它的getter方法now()提供了“时间”的概念。在具体调度器上安排的任务将严格遵循该时钟所表示的时间。
Scheduler类型
-
null
- 特点:立即执行任务,无延迟。
- 用途:用于同步执行任务,适合计算量小且不需要延迟的操作。
-
queueScheduler
- 特点:在当前事件循环中顺序执行任务。
- 用途:适用于需要顺序执行的任务,尤其是在递归或迭代操作中,以避免调用栈溢出。
- 备注: trampoline scheduler是一种特殊的调度器,用于在当前事件循环中管理任务的执行。它的工作方式类似于弹簧,先进入队列的任务会被推迟执行,直到当前的执行堆栈被清空。这意味着它会在当前事件循环的末尾执行任务,而不是立即执行,这有助于避免递归调用导致的堆栈溢出问题。
-
asapScheduler
- 特点:利用微任务队列,比setTimeout更快地异步执行任务。
- 用途:适用于需要快速响应的操作,但又不想在当前调用栈中执行的任务,如处理UI事件或进行微小的后台计算。
-
asyncScheduler
- 特点:使用setInterval来异步执行任务。
- 用途:适合于需要延迟或定时执行的任务。可以用来替代setTimeout和setInterval。
- 备注: 时间相关的操作符中,会默认使用该调度。
-
animationFrameScheduler
- 特点:利用requestAnimationFrame来同步浏览器的重新渲染,适合动画和UI渲染。
- 用途:在进行动画或要求高性能的UI更新时使用,可以保证任务在每个渲染帧开始时执行。
Scheduler的使用
-
subscribeOn
- 用途: subscribeOn用于指定Observable的订阅(subscribe)应该在哪个Scheduler上发生。
- 作用方式: 使用subscribeOn(scheduler),你可以延迟订阅或在指定的调度器上安排订阅动作。这里的scheduler是你提供的参数,指示订阅操作应该在哪个调度器上执行。这意味着,无论Observable何时被创建,通过使用subscribeOn,订阅动作的执行可以被延迟或安排在特定的时间点执行,而不是立即发生。
-
observeOn
- 用途:observeOn用于指定Observable发出的通知应该在哪个调度器上传递给观察者。
- 作用方式:使用observeOn(scheduler),在sourceObservable和Observer之间引入了一个中间人(mediator Observer)。这个中间人使用给定的Scheduler来安排对Observer的调用。这样,即使sourceObservable同步地生成和发出值,使用observeOn也可以确保Observer的回调函数在指定的调度器上异步地发生。