优化 RxJS 改造示例

原则

统一风格

  • 统一的代码风格,不能一次代码提交中各种复制粘贴的痕迹。
  • 如果你不理解遍历器及状态机的时候,用 Promise (查看例1 getDefer)
  • 不需要复杂状态机或者遍历器的时候,不要用 Generator (详情见例2)
  • 不需要异步处理多个 Promise 对象的时候,不要用 Async/Await (详情见例2)

例1 getDefer

在 jQuery、 Q.js 等中都有 defer 对象的封装。

实现

这里写了一个精简的 defer 实现:

/**
 * getDefer
 * @return {Promise.defer} defer对象
 */
exports.getDefer = () => {
  const deferred = {};
  deferred.promise = new Promise((resolve, reject) => {
    deferred.resolve = resolve;
    deferred.reject = reject;
  });
  return deferred;
};

使用

// 正常情况下,无须 */async 包裹
function fn() {
  const deferred = getDefer();
  // 传统方法改造
  originFn(...args, (err, result) => {
    // 错误结果 reject
    if (err) {
      deferred.reject(err);
    }
    // 正确结果 resolve
    deferred.resolve(result);
  });
  // 不关注 callback 方法是否执行完成,返回 deferred 对象
  return deferred.promise;
}

示例

改造传统 callback 方法的示例:

function xhrRequest(url) {
  const deferred = getDefer();
  request.get(url, { timeout: 1e4 }, (err, res) => {
    if (err) {
      deferred.reject(err);
    }
    try {
      deferred.resolve(JSON.parse(res.body));
    } catch (e) {
      deferred.reject(err);
    }
  });
  return deferred.promise;
}

替换方案

同时,node.js 原生 Utilities 中已经有了 Promisify 方法来方便 Promise 改造。 参考官方文档: https://nodejs.org/dist/latest-v16.x/docs/api/util.html#util_util_promisify_original

例2 Generator

理解绕弯,调试不便。一般用于底层复杂业务处理及性能优化。

注意:如果不理解迭代器方法及状态机,不要使用 Generator。切记!切记!

原理

解释一下 Generator 函数工作的流程:

function* foo(x) {
  var y = 2 * (yield (x + 1));
  console.log(x,y)
  var z = yield (y / 3);
  console.log(x,y,z)
  return (x + y + z);
}

// 此时创建, x = 2
var a = foo(2);

// 第一次调用,传值无效
// 执行 yield (x + 1) -> 3
// 停在 L2
console.log(a.next());
// { value: 3, done: false }

// 第二次调用,传入 3
// 替换 var y = 2 * 3
// 执行 yield(y / 3) -> 2
// 停在 L4
console.log(a.next(3));
// 2 6
// { value: 2, done: false }

// 第三次调用,传入 1
// 替换 var z = 1
// return x + y + z 结束
console.log(a.next(1));
// 2 6 1
// { value: 9, done: true }

迭代器示例

将嵌套的数组(Nested Array)打平(成 Plain Array)。

function* iterArr(arr) {
  // 迭代器返回一个迭代器对象
  if (Array.isArray(arr)) {
    // 内节点
    for (let i = 0; i < arr.length; i += 1) {
      yield* iterArr(arr[i]); // (*)递归
    }
  } else {
    yield arr;
  }
}
// 使用 for-of 遍历:
var arr = ['a', ['b', 'c'], ['d', 'e']];
for (const x of iterArr(arr)) {
  console.log(x); // a  b  c  d  e
}

// 或者直接将迭代器展开:
var arr = ['a', ['b', ['c', ['d', 'e']]]];
const gen = iterArr(arr);
arr = [...gen];
console.log(arr);
// ["a", "b", "c", "d", "e"]

例3 Async/Await

避免无意义的 async 包裹

Async/Await 仅是针对于 Promise 的一种语法糖,有很多时候,被误用了。先举几个常见的例子:

// BAD
async function test() {
  // 一些同步操作
  return "hello";
}

// GOOD
function test() {
  // 一些同步操作
  return "hello";
}
// BAD
async function test () {
  // 一些同步操作
  return await promiseFn();
}

// GOOD
function test () {
  // 一些同步操作
  return promiseFn();
}
// BAD
async function test () {
  // 一些同步操作
  const data = await promiseFn();
  return { result: data };
}

// GOOD
function test () {
  // 一些同步操作
  return promiseFn().then((data)=>({ result: data }));
}

Generator 改造为 Async

原始方法:

*createNode({ payload }, { call }) {
  const res = yield call(API.createNode, payload);
  const { statusCode, result } = res;
  const succMessage = '节点创建请求发起成功';
  const failMessage = '节点创建请求发起失败';
  if (statusCode === 'ok' && result) {
    notification.success({ message: result.message || succMessage, top: 64, duration: 3 });
    return true;
  }
  notification.error({ message: result.message || failMessage, top: 64, duration: 3 });
  return false;
}

async 改造:

async createNode({ payload }, { call }) {
  const res = await call(API.createNode, payload);
  const { statusCode, result } = res;
  const succMessage = '节点创建请求发起成功';
  const failMessage = '节点创建请求发起失败';
  if (statusCode === 'ok' && result) {
    notification.success({ message: result.message || succMessage, top: 64, duration: 3 });
    return true;
  }
  notification.error({ message: result.message || failMessage, top: 64, duration: 3 });
  return false;
}

优化:

// 普通方法
function notify({ statusCode, result } = {}) {
  const succMessage = '节点创建请求发起成功';
  const failMessage = '节点创建请求发起失败';
  if (statusCode === 'ok' && result) {
    notification.success({ message: result.message || succMessage, top: 64, duration: 3 });
    return true;
  }
  notification.error({ message: result.message || failMessage, top: 64, duration: 3 });
  return false;
}
// 去除包裹
createNode({ payload }, { call }) {
  return call(API.createNode, payload).then(notify);
}

RxJS 改造

React 中使用 RxJS

原理

简单的实现:

export const useObservable = (observable) => {
  const [value, setValue] = useState()
  const [error, setError] = useState()

  useEffect(() => {
    const subscription = observable.subscribe(setValue, setError)
    return () => subscription.unsubscribe()
  }, [observable])

  return [error, value]
}

更多 useXxx 参考: https://github.com/streamich/react-use

这里推荐两个封装好的库:

  • https://github.com/LeetCode-OpenSource/rxjs-hooks
  • https://github.com/crimx/observable-hooks

向 RxJS 改造

还是刚才例3 中的代码

import { from } from 'rxjs';
import { map } from 'rxjs/operators';
// ...

createNode({ payload }, { call }) {
  // 其中 API 调用还可以进一步改造
  // 如果改造完成,为一个 Observable 或者 Observer,则不再需要 From 转化,直接流式调用即可
  return from(call(API.createNode, payload)).pipe(
  	map(notify)
  );
}

调用 Promise 方法

import { from } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

from(promiseFn).pipe(
  mergeMap((result)=>{
    // 处理 promise result
    return result;
  })
);

Promise 改造为 Observable

创建一个 Observable 对象示例:

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  setTimeout(() => {
    subscriber.next(4);
    subscriber.complete();
  }, 1000);
});

使用 AsyncSubject 对象示例:

自行了解,较为复杂,需要先了解多播(MultiCasting)。

将一个事件转为 Observable

示例: 在线运行

import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

// 侦听点击事件
const source = fromEvent(document, 'click');
// 绑定事件处理方法
const example = source.pipe(map(event => `Event time: ${event.timeStamp}`));
// 输出 (示例): 'Event time: 7276.390000000001'
const listener = example.subscribe(val => console.log(val));

// 取消事件绑定
// listener.unsubscribe();

调用 Observable

三个方法均为可选参数:

  • next : 每执行一步
  • complete: 结束时执行
  • error: 出错执行(注意内部报错需要用 throwError 方法)
import { of } from 'rxjs';

const source$ = of([1, 2, 3]);

source$.subscribe({
  next: (val) => {
    console.log(val);
  },
  complete: () => {
    console.log('done');
  },
  error: (err) => {
  	console.error(err);
  }
})

参考文档: https://rxjs.dev/guide/observer

经典实例

Debounce 防抖

常用方法,使用场景就不用说了,比如 EventListener 中侦听 scroll 事件等。

示例:在线运行

import { interval, timer } from 'rxjs';
import { debounce } from 'rxjs/operators';

// 每秒投射(Emit)一个值,如 0, 1, 2, 3 ....
const interval$ = interval(1000);
// debounce 时间动态增加 200ms 每次执行
const debouncedInterval = interval$.pipe(debounce(val => timer(val * 200)));
/*
  5 秒钟之后, debounce 时间将会超过定时器时间 
  输出: 0...1...2...3...4......(debounce 时间大于 1s,不再投射值出来)
*/
debouncedInterval.subscribe(val =>
  console.log(`Example Two: ${val}`)
);

Throttle 限流

同理,限流(Throttle)也是 RxJS 内置的一个操作符(Operator)。

示例:

import { interval } from 'rxjs';
import { throttleTime } from 'rxjs/operators';

// 每秒投射
const source = interval(1000);
/*
  投射一个值, 然后忽略 5s,反复
*/
const example = source.pipe(throttleTime(5000));
// 输出: 0...6...12
example.subscribe(val => console.log(val));

Retry 出错重试

参考: http://rx.js.cool/advanced/delayRetry

undo 撤销操作

参考: http://rx.js.cool/mixin/undo


深入阅读: http://rx.js.cool

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Willin 老王躺平不摆烂

感谢你这么好看还这么慷慨

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值