Rx.js实现原理浅析
前言
上次给大家分享了cycle.js的内容,这个框架核心模块的代码其实只有一百多行,要理解这个看似复杂的框架,其实最核心的是理解它依赖的异步数据流处理框架——rx.js。今天,给大家分享一下rx.js的实现原理,大家有兴趣可以继续研究它的源码,会让你对异步和响应式编程有更深的理解,进而将rx.js、cycle.js或者仅仅是函数式、响应式编程的思想融入到自己手里的业务中。
为了更好地理解rx.js,需要先谈谈异步编程的实现方案。
异步实现方案
1. 回调函数
makeHttpCall('/items',
items => {
for (itemId of items) {
makeHttpCall(`/items/${itemId}/info`,
itemInfo => {
makeHttpCall(`/items/${itemInfo.pic}`,
img => {
showImg(img);
});
});
}
});
beginUiRendering();
一旦你需要多块数据时你就陷入了流行的”末日金字塔“或者回调地狱。这段代码有很多的问题。 其中之一就是风格。当你在这些嵌套的回调函数中添加越来越多的逻辑,这段代码就会变得很复杂很难理解。因为循环还产生了一个更加细微的问题。for循环是同步的控制流语句,这并不能很好的配合异步调用,因为会有延迟,这可能会产生很奇怪的bug。
2. Promise
makeHttpCall('/items')
.then(itemId => makeHttpCall(`/items/${itemId}/info`))
.then(itemInfo => makeHttpCall(`/items/${itemInfo}.pic}`))
.then(showImg);
链式调用毫无疑问是一个进步,理解这段代码的难度显著下降。然而,尽管Promises在处理这种单值(或单个错误)时非常高效,它有也一些局限性。Promises在处理用户连续输入的数据流时效率怎么样呢? 这时Promises处理起来也并不高效,因为它没有事件的删除、分配、重试等等的语法定义。
3. async/await
使用async/await,配合Promise可以以同步的方式编写异步代码,是我现在最喜欢也最常用的异步编程方式。比如:上述实现在请求加载完图片后再显示图片的逻辑,也可以这样实现:
const showLoadedImg = async () => {
let getImgInfo = makeHttpCall('/items')
.then(itemId => makeHttpCall(`/items/${itemId}/info`))
.then(itemInfo => makeHttpCall(`/items/${itemInfo}.pic}`));
let loadedImg = await getImgInfo;
showImg(loadedImg);
}
4. generator
generator我用的很少,之前用过一段时间koa(express原班团队搞的基于generator的现代web开发框架),不太喜欢代码在各个模块间跳来跳去的编写思路。整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。调用 Generator 函数,会返回一个内部指针(即遍历器 )g,调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句。下面贴一段koa中间件级联的代码,大家感受一下:
var koa = require('koa');
var app = koa();
// x-response-time
app.use(function *(next){
var start = new Date;
yield next;
var ms = new Date - start;
this.set('X-Response-Time', ms + 'ms');
});
// logger
app.use(function *(next){
var start = new Date;
yield next;
var ms = new Date - start;
console.log('%s %s - %s', this.method, this.url, ms);
});
// response
app.use(function *(){
this.body = 'Hello World';
});
app.listen(3000);
上面的例子在页面中返回 "Hello World",然而当请求开始时,请求先经过 x-response-time 和 logging 中间件,并记录中间件执行起始时间。 然后将控制权交给 reponse 中间件。当中间件运行到 yield next 时,函数挂起并将控制前交给下一个中间件。当没有中间件执行 yield next 时,程序栈会逆序唤起被挂起的中间件来执行接下来的代码。
5. rxjs
RxJS是一个解决异步问题的JS开发库.它起源于 Reactive Extensions 项目,它带来了观察者模式和函数式编程的相结合的最佳实践。 观察者模式是一个被实践证明的模式,基于生产者(事件的创建者)和消费者(事件的监听者)的逻辑分离关系。
况且函数式编程方式的引入,如说明性编程,不可变数据结构,链式方法调用会使你极大的简化代码量。(和回调代码方式说再见吧)
如果你熟悉函数式编程,请把RxJS理解为异步化的Underscore.js。
RxJS 引入了一个重要的数据类型——流(stream)。
rxjs实现原理
观察者模式
观察者模式在 Web 中最常见的应该是 DOM 事件的监听和触发。
订阅:通过 addEventListener 订阅 document.body 的 click 事件。
发布:当 body 节点被点击时,body 节点便会向订阅者发布这个消息。
document.body.addEventListener('click', function listener(e) {
console.log(e);
},false);
document.body.click(); // 模拟用户点击
迭代器模式
迭代器模式可以用 JavaScript 提供了 Iterable Protocol 可迭代协议来表示。Iterable Protocol 不是具体的变量类型,而是一种可实现协议。JavaScript 中像 Array、Set 等都属于内置的可迭代类型,可以通过 iterator 方法来获取一个迭代对象,调用迭代对象的 next 方法将获取一个元素对象,如下示例。
var iterable = [1, 2];
var iterator = iterable[Symbol.iterator]();
iterator.next(); // => { value: "1", done: false}
iterator.next(); // => { value: "2", done: false}
iterator.next(); // => { value: undefined, done: true}
元素对象中:value 表示返回值,done 表示是否已经到达最后。
遍历迭代器可以使用下面做法。
var iterable = [1, 2];
var iterator = iterable[Symbol.iterator]();
while(true) {
let result;
try {
result = iterator.next(); // <= 获取下一个值
} catch (err) {
handleError(err); // <= 错误处理
}
if (result.done) {
handleCompleted(); // <= 无更多值(已完成)
break;
}
doSomething(result.value);
}
主要对应三种情况:
获取下一个值
调用 next 可以将元素一个个地返回,这样就支持了返回多次值。
无更多值(已完成)
当无更多值时,next 返回元素中 done 为 true。
错误处理
当 next 方法执行时报错,则会抛出 error 事件,所以可以用 try catch 包裹 next 方法处理可能出现的错误。
RxJS 的观察者 + 迭代器模式
RxJS 中含有两个基本概念:Observables 与 Observer。Observables 作为被观察者,是一个值或事件的流集合;而 Observer 则作为观察者,根据 Observables 进行处理。
Observables 与 Observer 之间的订阅发布关系(观察者模式) 如下:
订阅:Observer 通过 Observable 提供的 subscribe() 方法订阅 Observable。
发布:Observable 通过回调 next 方法向 Observer 发布事件。
下面为 Observable 与 Observer 的伪代码:
// Observer
var Observer = {
next(value) {
alert(`收到${value}`);
}
};
// Observable
function Observable (Observer) {
setTimeout(()=>{
Observer.next('A');
},1000)
}
// subscribe
Observable(Observer);
上面实际也是观察者模式的表现,那么迭代器模式在 RxJS 中如何体现呢?
在 RxJS 中,Observer 除了有 next 方法来接收 Observable 的事件外,还可以提供了另外的两个方法:error() 和 complete(),与迭代器模式一一对应。
var Observer = {
next(value) { /* 处理值*/ },
error(error) { /* 处理异常 */ },
complete() { /* 处理已完成态 */ }
};
结合迭代器 Iterator 进行理解:
next()
Observer 提供一个 next 方法来接收 Observable 流,是一种 push 形式;而 Iterator 是通过调用 iterator.next() 来拿到值,是一种 pull 的形式。
complete()
当不再有新的值发出时,将触发 Observer 的 complete 方法;而在 Iterator 中,则需要在 next 的返回结果中,当返回元素 done 为 true 时,则表示 complete。
error()
当在处理事件中出现异常报错时,Observer 提供 error 方法来接收错误进行统一处理;Iterator 则需要进行 try catch 包裹来处理可能出现的错误。
下面是 Observable 与 Observer 实现观察者 + 迭代器模式的伪代码,数据的逐渐传递传递与影响其实就是流的表现。
// Observer
var Observer = {
next(value) {
alert(`收到${value}`);
},
error(error) {
alert(`收到${value}`);
},
complete() {
alert("complete");
},
};
// Observable
function Observable (Observer) {
[1,2,3].map(item=>{
Observer.next(item);
});
Observer.complete();
// Observer.error("error message");
}
// subscribe
Observable(Observer);
RxJS 基础实现
有了上面的概念及伪代码,那么在 RxJS 中是怎么创建 Observable 与 Observer 的呢?
创建 Observable
RxJS 提供 create 的方法来自定义创建一个 Observable,可以使用 next 来发出流。
var Observable = Rx.Observable.create(observer => {
observer.next(2);
observer.complete();
return () => console.log('disposed');
});
创建 Observer
Observer 可以声明 next、err、complete 方法来处理流的不同状态。
var Observer = Rx.Observer.create(
x => console.log('Next:', x),
err => console.log('Error:', err),
() => console.log('Completed')
);
最后将 Observable 与 Observer 通过 subscribe 订阅结合起来。
var subscription = Observable.subscribe(Observer);
关于rx.js的使用和API就不再赘述了,理解了其实现原理,使用起来就很简单了!
Rx.js实现原理浅析的更多相关文章
- HTTP长连接和短连接原理浅析
原文出自:HTTP长连接和短连接原理浅析
- Javascript自执行匿名函数(function() { })()的原理浅析
匿名函数就是没有函数名的函数.这篇文章主要介绍了Javascript自执行匿名函数(function() { })()的原理浅析的相关资料,需要的朋友可以参考下 函数是JavaScript中最灵活的一 ...
- rx.js 的冷和热观察
http://cn.rx.js.org/manual/overview.html#h213 https://rxjs-cn.github.io/rxjs5-ultimate-cn/content/ho ...
- [转帖]Git数据存储的原理浅析
Git数据存储的原理浅析 https://segmentfault.com/a/1190000016320008 写作背景 进来在闲暇的时间里在看一些关系P2P网络的拓扑发现的内容,重点关注了Ma ...
- Android-Binder原理浅析
Android-Binder原理浅析 学习自 <Android开发艺术探索> 写在前头 在上一章,我们简单的了解了一下Binder并且通过 AIDL完成了一个IPC的DEMO.你可能会好奇 ...
- Dubbo学习(一) Dubbo原理浅析
一.初入Dubbo Dubbo学习文档: http://dubbo.incubator.apache.org/books/dubbo-user-book/ http://dubbo.incubator ...
- 沉淀,再出发:docker的原理浅析
沉淀,再出发:docker的原理浅析 一.前言 在我们使用docker的时候,很多情况下我们对于一些概念的理解是停留在名称和用法的地步,如果更进一步理解了docker的本质,我们的技术一定会有质的进步 ...
- 阻塞和唤醒线程——LockSupport功能简介及原理浅析
目录 1.LockSupport功能简介 1.1 使用wait,notify阻塞唤醒线程 1.2 使用LockSupport阻塞唤醒线程 2. LockSupport的其他特色 2.1 可以先唤醒线程 ...
- 【Spark Core】TaskScheduler源代码与任务提交原理浅析2
引言 上一节<TaskScheduler源代码与任务提交原理浅析1>介绍了TaskScheduler的创建过程,在这一节中,我将承接<Stage生成和Stage源代码浅析>中的 ...
随机推荐
- day10 字符编码
字符编码 在python中出现乱码就是字符编码没有匹配的问题 python3中执行python3编辑的代码只要没有修改过编码,都是用utf-8,如果出现乱码就修改头文件,改成和原来编码相同的字符编码 ...
- 如何让元素支持 height:100%效果
如何让元素支持 height:100%效果? 有两种方法.(1) 设定显式的高度值. 这个没什么好说的,例如,设置 height:600px,或者可以生效的百分比值高度.例如,我们比较常见的:html ...
- Tomcat更改错误页面指向,改变404,500错误页面
在公司工作了一段时间,也被安排做了一个App,而且后台也是我来写和布置的,由于一次安全检查,需要我把tomcat默认页(管理页面)关闭,于是我只能进行默认指向变更,但是后面我又想到要是用户输入不存在的 ...
- Scala 面向对象(二):package 包 (一) 入门
1 Scala包的基本介绍 和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些,下面我们学习Scala包的使用和注意事项. 2 Scala包快速入门 使 ...
- CSS之Bootstrap(快速布局)
简介 什么是Bootstrap? Bootstrap官网 框架:库 lib library jQuery作为一个框架来讲,提供一套比较便捷的操作DOM的方式 把大家都需要的功能预先写好到一些文件 这就 ...
- Python爬虫学习02--pyinstaller
Python爬虫学习02--打包exe可执行程序 1.上一次做了一个爬虫爬取电子书的Python程序,然后发现可以通过pyinstaller进行打包成exe可执行程序.发现非常简单好用 2.这是上次写 ...
- redis linux开机启动 (简单高效)
1. 在edis下载文件包中找 redis/utils 找到redis_init_script 将它拷贝到 /etc/init.d 目录并重命名为redis cd redis cd utils mv ...
- (2)简单理解和使用webpack-dev-server
webpack-dev-server能做什么? 每次打包都得像之前一样使用webapck 入口文件 -o 出口文件,每次修改都得打包一次过于麻烦,可以使用webpack-dev-server实现自动打 ...
- xenomai内核解析之信号signal(二)---xenomai信号处理机制
xenomai信号 上篇文章讲了linux的信号在内核的发送与处理流程,现在加入了cobalt核,Cobalt内核为xenomai线程提供了信号机制.下面一一解析xenomai内核的信号处理机制. 1 ...
- 大型Java进阶专题(九) 设计模式之总结
前言 关于设计模式的文章就到这里了,学习这门多设计模式,你是不是有这样的疑惑,发现很多设计模式很类似,经常会混淆某些设计模式.这章节我们将对设计模式做一个总结,看看各类设计模式有什么区别.需要注意 ...