原来 RxJS 是这样处理复杂数据流

一、本文适合对象

  • 对 RxJS 感兴趣,有探索欲望
  • 熟悉函数式编程和面向对象范式
  • 对 RxJS 有基础了解
  • 熟悉 RxJS 操作符

二、rxjs 处理复杂业务

有时候我们要把 RxJS 理解为一个中 编程语言, 因为有自己的编程范式。数据必须是要在客观对象的中包裹或者pipe函数中处理的。

三、为什么会复杂?

之所复杂其实就是 RxJS 处理复杂的过程逻辑与面向对象和过程式有很大的不同,需要 RxJS 化,思考方式需要流的知识点支持,需要 RxJS 基于管道函数式编程思想的支持与实践。

四、普通程序复杂

  • 单/多数据,在普通的程序中,数据通常就是变量中保存。
  • 处理异步,异步处理方式:callback/promise/async-await。
  • 数据转换,根据不同业务需求,需要进行不同的转换
  • 条件判断,不同的条件干不同的事情
  • 复杂逻辑抽象函数,将复杂的内容抽象化,防止代码复杂
  • 复用代码,抽象出重复的代码,方便维护
  • 错误处理,出错处理往往与业务结合。

五、数据流向

数据流变化:可观察对象倾向与使用 push 方式处理数据,有一个数据源,但订阅之后,使用 push方式获取数据。

ts 复制代码
function handle() {
    return {}
}

const data = handle() 

data 在 handle 函数发生调用之后, 拉取到。而 RxJS 是数据根据订阅推送的

一个可观察对象就是一个数据流:

  • 静态数据流: of(1)/from(['a'])
  • promise 流
  • ...

组合数组流

场景:当单个数据流,携带的数据不能满足需求,此时就需要组合流,组合的方式有很多种,这里着重介绍以下几种

  • combineLatest([ob$1, ob$2]): 组合最新的,特点是有新的值就发出一组数据。
  • forkJoin(): 支持 数组,对象等形式,特点式,发出流中的最后一个,如果其中一个组合一致没有发出值,就会被挂起,此时可能需要额外的处理。

流中数据处理

  • map系列:转换
  • filter:过滤
  • 数学计算
  • CRUD
  • 组合

切换流

当我们完成第一个流任务,然后需要进入下一个流,此时我们就需要切换流。

  • switchMap 就是一个用具抓换数据并切换的操作符。有了流的切换,意味着我们就具有:创建 RxJS 无限流可能,一个 RxJS 流可完成我们想要的众多任务

条件判断

在流中数据返回可能需要我们进行判断:

  • iif 就是一个判断操作符,第一参数是判断函数,需要返回 boolean 值,第二个和第三个都是可观察对象,分别是对错流。

错误处理

  • 捕获错误:catchError
  • 抛出错误:throwError

错误处理必要的,throwError 和 catchError 都需要接收函数,catchError 用于捕获当前 pipe 中的错误。

调试困难

在 RxJS 中由于函数占据主流,箭头函数使得编程变得简单,但是箭头函数在返回一个内容时不利于调试。在 pipe 管道中等更加不好直接调试。这样使得编程时候增加了难度。

处理一个复杂的逻辑

以下是一个登录流程:

  • 获取 session 并校验是否登录
  • 登录 dto 数据
  • 校验 dto(成功-失败处理)
  • 数据库查询(基于 prisma)
  • 对比密码
  • 根据对比结果条件判断
  • 写入登录日志
  • 重定向到 dashboard

以上大概包含了 7 显示的任务,如果使用 rxjs 以下面的形式完成:

post 方法要求返回一个 Response/null/...,我们这在处理错误或者路由跳转都将其封装到函数,当 RxJS 数据完成时,调用函数即可。

ts 复制代码
static async post({ request, params }: ActionFunctionArgs) {
    const session$ = from(getSession(request.headers.get("Cookie")));
    const lang$ = of(params?.lang).pipe(defaultIfEmpty(defaultLang));
    const dataDto$ = from(request.json());

    const crreateErrorHandle = (message?: string) => () => {
      return respUtils.respFailJson(
        {},
        message ?? "登录失败,用户名或密码错误!",
      );
    };
    const redirectToDashboard =
      (url: string, cookie: string, lang: string) => () => {
        return redirect(`/${lang}/admin/dashboard`, {
          headers: {
            "Set-Cookie": cookie,
          },
        });
      };

    const user$ = dataDto$.pipe(
      switchMap((dataDto) => findByUserName$(dataDto.username)),
      catchError((e) => throwError(crreateErrorHandle(e ?? "未注册"))),
    );

    const loginResult$ = forkJoin([dataDto$, user$, session$]).pipe(
      switchMap((v) => {
        const [dataDto, user, session] = v;
        return iif(
          () => dataDto === null,
          from([]).pipe(
            switchMap(() => {
              session.flash("error", "Invalid username/password");
              return throwError(
                crreateErrorHandle("Invalid username/password"),
              );
            }),
          ),
          of([dataDto, user]).pipe(
            tap(() => {
              session.set("userId", String(user?.id));
            }),
          ),
        );
      }),
      map((v) => ({
        user: v[1],
        passwordMatch: comparePassword(v[0].password, v[1].password),
      })),
      switchMap(({ user, passwordMatch }) => {
        return iif(
          () => passwordMatch,
          of(user),
          throwError(crreateErrorHandle("Invalid username/password")),
        );
      }),
      switchMap((user) =>
        from(getLoginInfo(request)).pipe(
          map((loginLog) =>
            loginLogSchema.parse({ ...loginLog, name: user.name }),
          ),
          switchMap((validateLoginLog) =>
            from(createLoginLog({ ...validateLoginLog })),
          ),
          switchMap(() => of(user))
        ),
      ),
    );

    const url$ = forkJoin([loginResult$, lang$]).pipe(
      map((v) => `/${v[1]}/admin/dashboard?${v[0].username}`),
    );
    const result$ = url$.pipe(
      switchMap((url) =>
        from(session$).pipe(
          switchMap((session) =>
            forkJoin([of(url), from(commitSession(session)), lang$]),
          ),
          map((data) => ({ url: data[0], cookie: data[1], lang: data[2] })),
          map(({ url, cookie, lang }) => {
            return redirectToDashboard(url, cookie, lang!);
          }),
        ),
      ),
      catchError((e) => {
        return throwError(crreateErrorHandle(e.message));
      }),
    );

    const handleOver = await lastValueFrom(result$);
    return handleOver();
  }

这是一个复杂的流,想想从 async-await 转向 rxjs, 其实挺复杂的,在一个函数里面完成众多,当然时最能体现 RxJS 处理业务的能力,当然这个 RxJS 可能还可以够简化,这里就不在探索了。

小结

有时候我们需要将RxJS 看成一门编程语言,因为他有自己的流,似乎与主流的方向格格不入,但是在具备响应式和函数式特点,风格统一。使用 RxJS 编程需要我们在原来过程式或者面向对象式方式,跳转出来。在 RxJS 的流中切换和计算。同时需要与原始 JS 数据结构与来行进行转换,满足显示需求。是否有更好的 RxJS 使用编程使用,可以交流一下。

相关推荐
小刺猬_985几秒前
(超详细)数组方法 ——— splice( )
前端·javascript·typescript
渊兮兮2 分钟前
Vue3 + TypeScript +动画,实现动态登陆页面
前端·javascript·css·typescript·动画
鑫宝Code2 分钟前
【TS】TypeScript中的接口(Interface):对象类型的强大工具
前端·javascript·typescript
爱吃青椒不爱吃西红柿‍️9 分钟前
华为ASP与CSP是什么?
服务器·前端·数据库
一棵开花的树,枝芽无限靠近你13 分钟前
【PPTist】添加PPT模版
前端·学习·编辑器·html
凡人的AI工具箱13 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
陈王卜16 分钟前
django+boostrap实现发布博客权限控制
java·前端·django
java亮小白199721 分钟前
Spring循环依赖如何解决的?
java·后端·spring
景天科技苑23 分钟前
【vue3+vite】新一代vue脚手架工具vite,助力前端开发更快捷更高效
前端·javascript·vue.js·vite·vue项目·脚手架工具
SameX25 分钟前
HarmonyOS Next 安全生态构建与展望
前端·harmonyos