工作中一直使用的是Angular,也一直想对rxjs总结一下,把我再工作中用到的地方都总结一下。在Angular中已经深度集成了Rxjs,只要你使用Angular,那在项目中就会不可避免的接触Rxjs。其实,Rxjs也并不是Angular的专属框架,在其他框架里也是可以使用Rxjs的,后面会有例子。
本文中的例子代码采用的Rxjs版本为7.5.*。
什么是Rxjs?
RxJS 是 Reactive Extensions for JavaScript 的缩写,起源于 Reactive Extensions,是一个基于可观测数据流 Stream, 结合观察者模式和迭代器模式的一种异步编程的应用库。RxJS 是 Reactive Extensions 在 JavaScript 上的实现。Rxjs是一个库,它通过使用observable序列来编写异步和基于事件的程序。
为什么需要它?
在JS中有很多处理异步事件的场景,JS处理异步还是比较麻烦的,我之前有一篇文章写过JS中的异步流程控制,这里来一起简单回顾一下。
回调函数
JS中异步处理方式最简单的一种就是使用回调函数,但是这会产生臭名昭著的回调地狱。
下面这种场景在实际项目中还是会经常碰到的。
function delayFunc(){
setTimeout(() => {
console.log("t1");
setTimeout(() => {
console.log("t2");
setTimeout(() => {
console.log("t3");
}, 1000);
}, 1000);
}, 1000);
}
delayFunc();
有些人会觉得这挺清楚的啊?如果嵌套层次再深一点的话呢,而且每层的逻辑比较复杂呢,这样的代码超过3层的话,都是一种灾难。
generator函数
generator函数也可以实现异步流程控制,但是generator函数设计的初衷,是为了优雅的实现迭代,并不是为了异步流程控制。
function delayFunc(msg, cb){
setTimeout(() => {
console.log(msg);
cb && cb();
}, 1000);
}
function * myGen(cb){
yield delayFunc("t1", cb);
yield delayFunc("t2", cb);
yield delayFunc("t3", cb);
yield delayFunc("t4", cb);
}
const runner = myGen(goNext);
function goNext(res){
runner.next(res);
}
runner.next();
将上面代码再次封装的更加优雅。
function run(gen){
let it = gen(goNext);
function goNext(res){
it.next(res);
}
goNext();
}
run(myGen);
promise
Promise之前有详细的使用介绍,和原理的介绍,这里就不在赘述了。
function delayFunc(msg, cb){
setTimeout(() => {
console.log(msg);
cb && cb();
}, 1000);
}
const delayFuncPromise = (msg) => {
return new Promise((resolve) => {
delayFunc(msg, resolve);
});
};
delayFuncPromise("p1")
.then(() => {
return delayFuncPromise("p2");
})
.then(() => {
return delayFuncPromise("p3");
})
.then(() => {
console.log("完成了!");
});
Promise的方式明显和回调的方式有不一样的编码体验,它采用的是链式操作,而且能够进行异常捕获。比回调函数的方式更加丝滑,将异步事件的处理流程化,写法更加的优雅。
async/await
async/await语法糖天然支持Promise,是JS中异步的终极解决方案。让我们以同步的方式来书写异步代码,内部是Generator函数和自动执行器。
function delayFunc(msg, cb){
setTimeout(() => {
console.log(msg);
cb && cb();
}, 1000);
}
const delayFuncPromise = (msg) => {
return new Promise((resolve) => {
delayFunc(msg, resolve);
});
};
async function myAsyncFunc(){
let res = await delayFuncPromise("ps1");
await delayFuncPromise("ps2");
await delayFuncPromise("ps3");
}
myAsyncFunc();
Rxjs VS Promise
在初学Angular的时候,有的小伙伴知道在Angular中用Rxjs来处理异步非常的丝滑,可能会有这样的疑问,那其他框架里处理异步用什么呢?肯定是Promise了,这就说明Rxjs和Promise有点像,但是Rxjs的功能要比Promise强大很多,尽管我们知道Promise有几个我们常用的静态函数,resolve,all,race,allSettled等。
Rxjs不仅仅可以以流的形式对数据进行控制,还内置了众多的操作符,让我们能十分方便的处理各种层面的数据。
现在需要实现一个批量请求函数multipleRequest(urls, maxNum),要求如下:
- 最大并发数为maxNum;
- 每当有一个请求完成时,就留出一个空位,可以进行新的请求;
- 完成所有请求后,结果按照urls里的顺序返回结果;
假设http请求函数为:
function request(url){
return new Promise((resolve, reject) => {
// 模拟了异步请求
setTimeout(() => {
resolve(`Result: ${url}`);
}, 2000);
});
}
也就是说,有一个异步任务池,最大容量是maxNum,当池子里的一个任务执行完成后,新的任务可以放入池子中进行执行。这种异步的场景正是promise擅长的场景,所以我们用Promise实现一下。
server端:
const http = require('http');
const server = http.createServer((req, res) => {
console.log(req.url);
res.setHeader("Access-Control-Allow-Origin", '*');
if (req.url === '/') {
res.end("Home");
}
else if (req.url === '/account') {
res.end("zTom");
}
else if (req.url === '/user') {
res.end("Tom");
}
else if (req.url === '/contact') {
res.end("Jerry");
}
else if (req.url === '/friend') {
res.end("Kate");
}
});
server.listen(4000, () => {
console.log('server is running at port 4000...');
});
client端:
function multipleRequest(urls = [], maxNum) {
let len = urls.length;
// result里的结果的顺序要和urls里的返回值顺序保持一致
let result = new Array(len).fill(-1);
// 发出请求的个数
let count = 0;
return new Promise((resolve, reject) => {
// 先执行maxNum个请求
while(count < maxNum) {
next();
}
function next() {
let current = count++;
if (current >= len) {
return;
}
let url = urls[current];
fetch(url).then((res) => {
return res.text();
}).then((data) => {
result[current] = data;
// 所有请求完成则resolve,否则继续next
if (!result.includes(-1)) {
resolve(result);
}
else {
next();
}
})
.catch((err) => {
// 出错了
result[current] = err;
// 所有请求完成则resolve,否则继续next
if (!result.includes(-1)) {
resolve(result);
}
else {
next();
}
});
}
});
}
let urls = [
'http://localhost:4000',
'http://localhost:4000/user',
'http://localhost:4000/account',
'http://localhost:4000/contact',
'http://localhost:4000/friend'
];
multipleRequest(urls, 3).then((res) => {
console.log(res);
});
用Promise实现还是挺麻烦的,但是如果用Rxjs,它提供了优雅的接口,使用起来也很优雅。
我这里就模拟一下,request(url)是请求url,返回一个stream。
const { of, from, mergeMap} = require('rxjs');
function request(url){
return of(url+':1');
}
let urls = [
'http://localhost:4000',
'http://localhost:4000/user',
'http://localhost:4000/account',
'http://localhost:4000/contact',
'http://localhost:4000/friend'
];
from(urls).pipe(mergeMap(request, 3)).subscribe((res) => {
console.log(res);
});
Rxjs在处理异步的时候,代码量很少,语义性很强。但是它的学习成本也比较高,我也是在工作中用了几年,工作中的一些场景还是没问题的,但是并不精通,或者说站的高度还不够,继续深入吧。