异步数据流的响应式编程库Rxjs(一)前言篇

344 阅读5分钟

工作中一直使用的是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),要求如下:

  1. 最大并发数为maxNum;
  2. 每当有一个请求完成时,就留出一个空位,可以进行新的请求;
  3. 完成所有请求后,结果按照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在处理异步的时候,代码量很少,语义性很强。但是它的学习成本也比较高,我也是在工作中用了几年,工作中的一些场景还是没问题的,但是并不精通,或者说站的高度还不够,继续深入吧。