RxJS 系列 – Utility Operators

前言

前几篇介绍过了 

Creation Operators

Filtering Operators

Join Creation Operators

Error Handling Operators

Transformation Operators

Join Operators

这篇继续介绍 Utility Operators

 

参考

Docs – Utility Operators

 

tap

tap 是用来写 side effect 的. RxJS 也带有函数式的概念. 

一个 Observable 流是没有任何 side effect 的. 如果想搞 side effect 就可以利用 tap operator

from([1, 2, 3])
  .pipe(tap(v => console.log('tap: ' + v)))
  .subscribe();

效果

tap 不需要返回任何值, upstream 的 value 会自动被传到 downstream. tap 只负责 side effect 就可以了.

 

delay

顾名思义, 就是延后一个时间才接收.

console.log('start');
from([1, 2, 3])
  .pipe(delay(1000))
  .subscribe(v => console.log(v));
console.log('end');

下面左图是没有 delay 的效果, 它是同步的, 右图是有 delay 的效果, start end 是同步, 1 秒后 1,2,3 才接收.

你可能好奇为什么不是

1 > delay 1 秒 > 2 > delay 1 秒 > 3

而是

delay 1 秒 1,2,3

关于这点, 可以回看 RxJS 系列 – Scheduler 里面有解释到. 这里不展开了.

 

delayWhen

delayWhen 是 delay 的底层实现. 下面是 delay 的源码. 里面调用了 delayWhen

delayWhen 接收一个方法, 参数是源 source 发布的值, 返回一个 Observable. 这个 Observable 发布表示 delay 结束.

from([1, 2, 3])
  .pipe(delayWhen(v => timer(v * 1000)))
  .subscribe(v => console.log(v));

第一个进入 delayWhen 的值是 1. 于是 timer(1 * 1000), 然后 1 秒后发布.

第二个进入的值是 2, timer(2 * 1000) 2 秒后发布

3 就是三秒后发布

最终效果

dematerialize

dematerialize 可以让我们通过 next 的方式输出 next, error, complete 的效果.

例子说明

首先我们有个 Subject. 它发布 ObservableNotification. 这个是 RxJS 的一个特别 interface

const subject = new Subject<ObservableNotification<string>>();

发布长这样

subject.next({ kind: 'N', value: 'next value' });
subject.next({ kind: 'E', error: 'error value' });
subject.next({ kind: 'C' });

kind: 'N' | 'E' | 'C' 分别代表 Next, Error, Complete

接收长这样

subject.pipe(dematerialize()).subscribe({
  next: v => console.log('next', v),
  complete: () => console.log('complete'),
  error: e => console.log('error', e),
});

当发布 kind: 'E', subscribe 就会接收到 error.

当然如果直接调用 subject.complete 或 subject.error, subscribe 依然会收到 complete 和 error, dematerialize 只是扩展了 next 的表达, 并没有破坏任何原本的东西.

 

materialize

dematerialize 是让 next 变得可以发布 error, complete

materialize 则是把 .complete, .error 变成 next 发布

在 materialize 的情况下, subscribe 永远只需要处理 next, 因为永远都接收不到 complete 和 error

const subject = new Subject<string>();

subject.pipe(materialize()).subscribe({
  next: v => console.log(v),
  complete: () => console.log('complete'), // never be called
  error: e => console.log('error', e), // never be called
});

subject.next('value'); // console: { kind: 'N', value: 'value', error: undefined, hasValue: true }
subject.error('error'); // console: { kind: 'E', value: undefined, error: 'error', hasValue: false }

 

observeOn, subscribeOn

回看 RxJS 系列 – Scheduler, 里面已经介绍过了.

简单说

subscribeOn 是 delay subscribe, 但没有 delay 后续的发布 (放在 pipe 任何位置效果一样)

observeOn 是 delay 后续的发布, 但是没有 delay 源头 (放在 pipe 的位置不同效果不同)

schedule([1,2,3], asyncScheduler) 是源头开始 delay 发布.

3 种方式表示了不同阶段的 delay 

schedule 源头 > observeOn 中间 > subscribeOn 结尾

 

timeInterval

timeInterval 能让我们知道每一次发布距离上一次间隔了多久.

const subject = new Subject<string>();

subject.pipe(timeInterval()).subscribe(({ value, interval }) => console.log([value, interval]));

(async () => {
  await delayAsync(2000);
  subject.next('first');

  await delayAsync(4000);
  subject.next('second');
})();

效果

最终接收的 value 被 wrap 了一层对象. 里面包含了间隔时间和原本的值.

第一个 2 秒的间隔是从 subscribe() 开始到第一次的 next 发布. 2005 多了 5ms 是正常的, JS 单线程总是会有微差的.

第二个 4 秒就是从第一次发布到第二次发布的间隔时间.

 

timestamp

timestamp 和 timeInterval 类似. 只是它返回的不是间隔的 ms. 而是 Epoch Time.

subject.pipe(timestamp()).subscribe(({ value, timestamp }) => console.log([value, timestamp]));

效果

 

timeout

timeout 的概念就是限定一个时间内, 必须完成任务, 没有完成就一个特殊处理.

用在 RxJS 指的是一个 stream 必须在 timeout 限制的时间内, 完成发布. 没有发布就 failure, 然后就一个特殊处理.

const subject = new Subject<string>();
subject
  .pipe(
    timeout({
      each: 2000,
    })
  )
  .subscribe({
    next: () => console.log('next'),
    error: e => console.log('error', e),
    complete: () => console.log('complete'),
  });

上面的 timeout 要求 subject 从 subscribe() 开始, 每一次发布间隔都不可以超出 2 秒

(async () => {
  await delayAsync(1000);
  subject.next('ok1'); // 可以
  await delayAsync(1500);
  subject.next('ok2'); // 可以
  await delayAsync(2500); // timeout error, 从上一次发布已经超过了要求的 2 秒
  subject.next('ok3');
})();

效果

一旦超过时间, 就会触发 error

first

把 each 改成 first, 就变成只限制第一次的发布必须在时间内.

timeout({
  first: 2000,
})

效果

没有 error 了, 因为第一次发布在限定的 2 秒就 ok 了

Date

first 还支持绝对时间

timeout({
  first: new Date(2023, 1, 1),
})

只要在 01-01-2023 前发布就 ok, 超过这个时间就报错.

custom error handle

如果不希望 throw error, 我们可以自己设定处理方式

timeout({
  first: 1000,
  with: info => {
    console.log(info);
    return EMPTY;
  },
})

效果

通过 with 返回一个 Observable, downstream 会 subscribe 它. 上面例子我返回 EMPTY, 所以就进入了 subscribe 的 complete.

shorthand

timeout(1000) 
// 相等于
timeout({ each: 1000 })

timeout(new Date())
// 相等于
timeout({ first: new Date() })

 

toArray

toArray 有点像 buffer, 但是它比较简单明了.

当 Observable 还没有 complete 前, 所以发布的值会被保存起来, 不会接收.

一直到 Observable complete 以后, subscrube 会一次性接收到所有之前保存的值. 以 array 的方式接收.

const subject = new Subject<number>();
subject.pipe(toArray()).subscribe(v => console.log(v));
subject.next(1);
subject.next(2);
subject.next(3); // 到这里都不会触发 console
subject.complete(); // console: [1, 2, 3]

 

废弃了的 Transformation Operators

timeoutWith

 

一句话总结

tap : 处理 side effect

delay : 延迟发布 by 时间

delayWhen : 延迟发布 by Observable 

dematerialize : next 可以发布 complete 和 error

materialize : 把 complete 和 error 变成 next 发布

subscribeOn : delay subscribe, 但没有 delay 后续的发布 (放在 pipe 任何位置效果一样)

observeOn : delay 后续的发布, 但是没有 delay 源头 (放在 pipe 的位置不同效果不同)

timeInterval : wrap value with 从上一次发布到这一次的时间间隔

timestamp : wrap value with epoch time

timeout : 超时任务就特殊处理

toArray : 缓存所有 values 直到 complete 后一次性接收 by Array 形式

 

posted @ 2023-04-03 17:09  兴杰  阅读(53)  评论(0编辑  收藏  举报