Rxjs源码解析(五)Operators

对于开发者来说,rxjs 中最常接触的 api 莫过于 operator 了,按照功能的不同,目前版本的 rxjs 内置的 operator 分为十类:CreationJoin CreationTransformationFilteringJoinMulticastingError HandlingUtilityConditional and BooleanMathematical and Aggregate,在数量上则超过 100 个,能够满足几乎所有的场景,如果碰到无法很好适应的场景,也可以自定义一个属于自己的 operator

限于篇幅原因,所以不可能把这一百多个 operator 源码全部过一遍,也没必要,因为很多逻辑都共用的,我会从这十类 operator中,每类挑选几个作为范例进行源码解读

Creation

from

from 能够从 Arrayarray-likePromiseiterableObservable-likereadable-stream-like 这几类对象上创造出 Observable(即将这几类对象作为数据源),这几类对象几乎可以囊括所有的 js 对象了,所以 rxjsfrom 的注释是:Converts almost anything to an Observable

例如,从 Array

 import { from } from 'rxjs';

 const array = [10, 20, 30];
 const result = from(array);
 result.subscribe(x => console.log(x));
 // Logs:
 // 10
 // 20
 // 30
复制代码

iterable

import { from } from 'rxjs';
import { take } from 'rxjs/operators';
function* generateDoubles(seed) {
   let i = seed;
   while (true) {
     yield i;
     i = 2 * i;
   }
}
const iterator = generateDoubles(3);
const result = from(iterator).pipe(take(10));
result.subscribe(x => console.log(x));
// Logs:
// 3
// 6
// 12
// 24
// 48
// 96
// 192
// 384
// 768
// 1536
复制代码

既然能处理各种对象,那么其内部肯定是有对应的条件分支来做判断的,然后根据不同的对象类型,进入不同的处理方法,最终都生成一个 Observable

// node_modules/rxjs/src/internal/observable/from.ts
export function from<T>(input: ObservableInput<T>, scheduler?: SchedulerLike): Observable<T> {
  return scheduler ? scheduled(input, scheduler) : innerFrom(input);
}
复制代码

from 接收两个函数,第一个是数据源,第二个是 scheduler,之前文章已经说过这个东西了,就是让开发者有能力调度数据的流动逻辑,一般情况下,都是只传入第一个参数,那么就会执行 innerFrom方法

// node_modules/rxjs/src/internal/observable/innerFrom.ts
export function innerFrom<T>(input: ObservableInput<T>): Observable<T> {
  if (input instanceof Observable) {
    return input;
  }
  if (input != null) {
    if (isInteropObservable(input)) {
      return fromInteropObservable(input);
    }
    if (isArrayLike(input)) {
      return fromArrayLike(input);
    }
    if (isPromise(input)) {
      return fromPromise(input);
    }
    if (isAsyncIterable(input)) {
      return fromAsyncIterable(input);
    }
    if (isIterable(input)) {
      return fromIterable(input);
    }
    if (isReadableStreamLike(input)) {
      return fromReadableStreamLike(input);
    }
  }

  throw createInvalidObservableTypeError(input);
}
复制代码

innerFrom 方法用于将输入的对象 input 转为 observable。其根据传入的对象类型,选择不同的处理逻辑的方法,isXXX是用于判定类型的方法,fromXXX 则是将不同的对象转为 observable的方法,例如对于 fromPromise

export function fromPromise<T>(promise: PromiseLike<T>) {
  return new Observable((subscriber: Subscriber<T>) => {
    promise
      .then(
        (value) => {
          if (!subscriber.closed) {
            subscriber.next(value);
            subscriber.complete();
          }
        },
        (err: any) => subscriber.error(err)
      )
      .then(null, reportUnhandledError);
  });
}
复制代码

内部就是new Observable,然后再将这个实例返回

fromEvent

fromEvent 的类型签名还是比较多的,但总体来说就是四个参数

  • target

target的合法类型有三种: HasEventTargetAddRemoveNodeStyleEventEmitterJQueryStyleEventEmitter

export interface HasEventTargetAddRemove<E> {
  addEventListener(
    type: string,
    listener: ((evt: E) => void) | EventListenerObject<E> | null,
    options?: boolean | AddEventListenerOptions
  ): void;
  removeEventListener(
    type: string,
    listener: ((evt: E) => void) | EventListenerObject<E> | null,
    options?: EventListenerOptions | boolean
  ): void;
}
复制代码

对于 HasEventTargetAddRemove,很明显,是针对 HTMLElement的,所以可以传入 dom元素,类似的,NodeStyleEventEmitter允许传入 nodejsemitter对象,JQueryStyleEventEmitter则允许传入一个 jq 元素对象

  • eventName

事件名称,例如 clickfocus

  • options

类型签名:

export interface EventListenerOptions {
  capture?: boolean;
  passive?: boolean;
  once?: boolean;
}
复制代码

就是针对 targetdom元素的情况下,addEventListener的第三个参数

  • resultSelector

相当于一个拦截器,可以决定最终传递给 subscribe 的值是什么,例如

let i = 0
fromEvent(div, 'click', (v) => {
  if (i++ % 2 === 0) {
    return v
  }
  return null
}).subscribe(e => {
  console.log('click', e);
})
复制代码

第偶数次 click事件的触发,subscribe接收到的 e 是点击事件,而如果是第奇数次,则 e 就是 null

下面看源码

// rxjs/src/internal/observable/fromEvent.ts
export function fromEvent<T>(
  target: any,
  eventName: string,
  options?: EventListenerOptions | ((...args: any[]) => T),
  resultSelector?: (...args: any[]) => T
): Observable<T> {
  // ...
  const [add, remove] =
    // If it is an EventTarget, we need to use a slightly different method than the other two patterns.
    isEventTarget(target)
      ? eventTargetMethods.map((methodName) => (handler: any) => target[methodName](eventName, handler, options as EventListenerOptions))
      : // In all other cases, the call pattern is identical with the exception of the method names.
      isNodeStyleEventEmitter(target)
      ? nodeEventEmitterMethods.map(toCommonHandlerRegistry(target, eventName))
      : isJQueryStyleEventEmitter(target)
      ? jqueryMethods.map(toCommonHandlerRegistry(target, eventName))
      : [];
}
复制代码

isEventTargetisNodeStyleEventEmitterisJQueryStyleEventEmitter分别用于判断 targetdom元素还是 node event还是 jq对象,判断完了之后对不同类型的 target 执行对应的方法,返回 addremove

// rxjs/src/internal/observable/fromEvent.ts
const nodeEventEmitterMethods = ['addListener', 'removeListener'] as const;
const eventTargetMethods = ['addEventListener', 'removeEventListener'] as const;
const jqueryMethods = ['on', 'off'] as const;
复制代码

这里其实就是拿到 target 的添加监听事件方法和移除监听事件方法,然后在订阅的时候给 target 注册上监听事件,并在订阅清除的时候同时移除监听事件

// rxjs/src/internal/observable/fromEvent.ts
export function fromEvent<T>(
  target: any,
  eventName: string,
  options?: EventListenerOptions | ((...args: any[]) => T),
  resultSelector?: (...args: any[]) => T
): Observable<T> {
  //...
  return new Observable<T>((subscriber) => {
    const handler = (...args: any[]) => subscriber.next(1 < args.length ? args : args[0]);
    add(handler);
    return () => remove!(handler);
  });
}
复制代码

timer & interval

timer: 延迟发送通知(Used to emit a notification after a delay)

interval: 周期性地发送通知(即轮询)(Emits incremental numbers periodically in time)

// rxjs/src/internal/observable/timer.ts
export function timer(
  dueTime: number | Date = 0,
  intervalOrScheduler?: number | SchedulerLike,
  scheduler: SchedulerLike = asyncScheduler
): Observable<number> {}
复制代码

timer 的第一个参数是延迟时间,即决定延迟多长时间后执行,所以这是一个异步操作,内部会用到 scheduler

if (due < 0) {
  due = 0;
}
let n = 0;
return scheduler.schedule(function () {
  // ...
}, due);
复制代码

关于 scheduler 之前文章已经说过了,这里就不再多说了,这里 scheduler.schedule(function () {}, due);你可以看成是 setTimeout(function () {}, due);

第二个参数 intervalOrScheduler,如果传入的是一个数字,则表示在延迟 dueTime 时间之后,每隔 intervalOrScheduler 时间执行一次,就相当于是 setInterval,而实现轮询的方法,就是在 schedule 里再调用 schedule

return scheduler.schedule(function () {
  if (!subscriber.closed) {
    // Emit the next value and increment.
    subscriber.next(n++);

    if (0 <= intervalDuration) {
      // If we have a interval after the initial timer,
      // reschedule with the period.
      this.schedule(undefined, intervalDuration);
    } else {
      // We didn't have an interval. So just complete.
      subscriber.complete();
    }
  }
}, due);
复制代码

你也可以传入一个 scheduler,用于自定义调度,而如果你第二个参数传入了数字,并且还想传入一个 scheduler 的话,那么可以在第三个参数传入

intervaltimer 的一个封装版,可以看成是 rxjs 中的 setInterval

// /rxjs/src/internal/observable/interval.ts
export function interval(period = 0, scheduler: SchedulerLike = asyncScheduler): Observable<number> {
  if (period < 0) {
    period = 0;
  }
  return timer(period, period, scheduler);
}
复制代码

Join Creation Operators

concat

concat 可以将数个 Observables 组合成一个新的 Observable,并且在每个 Observable 结束后才继续执行下一個 Observable

const timer1 = interval(300).pipe(take(1));
const timer2 = interval(500).pipe(take(2));
const timer3 = interval(500).pipe(take(2));
复制代码

对于上述代码,想让 timer1 执行完了再开始执行 timer2timer2 执行完了再开始执行 timer3,如果不借助 concat 可以这么写:

timer1.subscribe({
  next: console.log,
  complete: () => timer2.subscribe({
    next: console.log,
    complete: () => timer3.subscribe({
      next: console.log
    })
  })
})
复制代码

这是我们应当避免写的 Callback Hell,如果借助 concat 就好多了

const result = concat(timer1, timer2, timer3)
result.subscribe(x => console.log(x))
复制代码
// /rxjs/src/internal/observable/concat.ts
export function concat(...args: any[]): Observable<unknown> {
  return concatAll()(from(args, popScheduler(args)));
}

// /rxjs/src/internal/util/args.ts
export function popScheduler(args: any[]): SchedulerLike | undefined {
  return isScheduler(last(args)) ? args.pop() : undefined;
}
复制代码

concatAll下面会提到,from 前文说过,就是将输入的值转换为 Observable的方法,argsconcat 的参数,也就是即将合并的数据流

那么源码的意思就比较明显了,即将 concat接收的数据流一个个地传递给 concatAll,由于 concatAll 具有顺序化执行数据流的功能,所以 concat传入的数据流也就必须一个个执行,上一个数据流结束了才能轮到下一个

Transformation Operators

mergeMap

把所有 Observable 合并到同一个数据流內,不会退订上一次的 Observable,也不会等待上一次的 Observable 结束,平行处理所有的 Observable

const letters = of('a', 'b', 'c')
const result = letters.pipe(
  mergeMap(x => interval(1000).pipe(map(i => x + i)))
)
result.subscribe(x => console.log(x))
// a0
// b0
// c0
// a1
// b1
// c1
// ...
复制代码

等价于

const letters = of('a', 'b', 'c')
letters.subscribe({
  next: x => {
    interval(1000).pipe(map(i => x + i)).subscribe(x => console.log(x))
  }
})
复制代码

所以实际上,mergeMap 就是进一步的封装(当然了,rxjs内基本所有操作符都是进一步封装的结果),如果存在 resultSelector,那么无需你在手动去 subscribe了,直接 mergeMap就行了

mergeMap 的源码,看到主要是调用了 mergeInternals,所以直接看这个

// /rxjs/src/internal/operators/mergeInternals.ts
const doInnerSub = (value: T) => {
  // ...
  innerFrom(project(value, index++)).subscribe(
    new OperatorSubscriber(
      subscriber,
      (innerValue) => {
        // 。。。
        subscriber.next(innerValue);
      },
      // ...
    )
复制代码

主要的就是 subscriber.next(innerValue) 这一句 project 是作为参数传入 mergeMap 的函数,value 是原 Observable 发出的值,index 代表是 Observable 第几次(从 0 开始)发出的事件,innerFrom 上面说过了,是将传入的参数转为 Observable 的方法

在这里,对 mergeMap 传入的 Observable进行了 subscribe,每次 Observable 发出值的时候,就会执行 subscriber.next(innerValue),而 subscriber 就是最外面 mergeMap 隶属的那条数据流

那么数据流动就很清晰了,mergeMap 对所有内部产生的数据流进行了订阅,并且在这些数据流产生数据的同时,对数据进行加工,再传给最外面唯一的主数据流,即数据的出口只有一个,从而给外界产生单数据流的结果

map

类似于 Array.prototype.map(),将 Observable 每次的值通过转换函数转成另外的值

const letters = of('a', 'b', 'c');
const result = letters.pipe(map((v, i) => v + i));
result.subscribe(x => console.log(x));

// a0
// b1
// c2
复制代码
// /rxjs/src/internal/operators/map.ts
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
  return operate((source, subscriber) => {
    let index = 0;
    source.subscribe(
      new OperatorSubscriber(subscriber, (value: T) => {
        subscriber.next(project.call(thisArg, value, index++));
      })
    );
  });
}
复制代码

operaterxjs 内部创建操作符的通用方法,很多操作符都会使用这个方法来创建,其内部主要是调用了 Observablelift 方法,方便操作符之间的链式调用的,这个之前文章已经说过了,至于 OperatorSubscriber 也就是一个 Subscriber,不再多说

map 里也是调用了 Observablesubscribe 方法,主要是 project.call(thisArg, value, index++) 这一句,project 就是 map传入的那个函数参数,这个函数接收两个参数,第一个参数就是原始 Observable发出的值,第二个参数记录目前的值是 Observable 第几次(从 0 开始)发出的事件,经过 map 处理之后的结果再交给 subscriber.next 做为最终值发出

可以看到,map回调函数的第二个参数 index,其初始化是在 subscribe 外面,所以每 subscribe 一次,其值就会递增

scan

作用类似于 reduce,并且会在每次更新的时候抛出当前的累计值

of(1, 2, 3).pipe(
  scan((total, c) => total + c, 0)
).subscribe(console.log)
// 1
// 3
// 6
复制代码

scan 内部调用了 scanInternals,直接看这个

// /rxjs/src/internal/operators/scanInternals.ts
let hasState = hasSeed;
let state: any = seed;
let index = 0;
source.subscribe(
  createOperatorSubscriber(
    subscriber,
    (value) => {
      const i = index++;
      state = hasState
        ? // We already have state, so we can get the new state from the accumulator
          accumulator(state, value, i)
        : // We didn't have state yet, a seed value was not provided, so

          // we set the state to the first value, and mark that we have state now
          ((hasState = true), value);

      // Maybe send it to the consumer.
      emitOnNext && subscriber.next(state);
    },
    // ..
  )
);
复制代码

scanInternals 内部维护了两个变量 stateindex,分别用户存储当前计算的累计值和当前累计的次数(从 0 开始),每次订阅的时候,都会将 state 累加上新传入的值,且将 index递增

switchMap

将上游的 Observable 映射成另外的 Observables,并且每次接到上游数据流的时候,都会取消对前一个映射数据流的订阅

timer(0, 2000).pipe(
  map(() => interval(500)),
  mergeAll(),
).subscribe(console.log)
// 0,1,2,3... 0,1,2,3...
复制代码

每隔 2000ms,上游就会产生新的数据流,被 switchMap 转换成其他的数据流,同时会取消对前一个数据流的订阅,所以上述代码会循环输出 0 1 2 3 4

而如果每次生成新数据流的时候,不取消对前一个数据流的订阅,那么就会出现多个数据流同时存在的情况,输出的结果就是多条数据流订阅结果的混合了,这个特性也正是 switchMap 和其他打平操作符的主要区别

// /rxjs/src/internal/operators/switchMap.ts
innerSubscriber?.unsubscribe();
let innerIndex = 0;
const outerIndex = index++;
// Start the next inner subscription
innerFrom(project(value, outerIndex)).subscribe(
  (innerSubscriber = createOperatorSubscriber(
    subscriber,
    (innerValue) => subscriber.next(resultSelector ? resultSelector(value, innerValue, outerIndex, innerIndex++) : innerValue),
    () => {
      innerSubscriber = null!;
      checkComplete();
    }
  ))
);
复制代码

innerSubscriber 就是上一个订阅的数据流,每次转换新的数据流之前,会调用 innerSubscriber?.unsubscribe();,也就是取消对数据流的订阅,innerFrom前面说过了,就是生成 observable 的方法,取消和重新生成的逻辑就在这短短几行代码里了

Filtering Operators

take

设定只从 Observable 中接收 N 次事件的值。当订阅开始后,如果发生过的事件次数已经达到我们设定的,就会结束当前数据流

interval(500).pipe(
  take(3)
).subscribe(console.log)
// 0
// 1
// 2
复制代码
// /rxjs/src/internal/operators/take.ts
export function take<T>(count: number): MonoTypeOperatorFunction<T> {
  return count <= 0
      () => EMPTY
    : operate((source, subscriber) => {
        let seen = 0;
        source.subscribe(
          createOperatorSubscriber(subscriber, (value) => {
            if (++seen <= count) {
              subscriber.next(value);
              if (count <= seen) {
                subscriber.complete();
              }
            }
          })
        );
      });
}

// /rxjs/src/internal/observable/empty.ts
export const EMPTY = new Observable<never>((subscriber) => subscriber.complete());
复制代码

当设定次数小于 0 时,立即结束(完成)当前数据流,这是个边界判断

否则,会有个变量 seen 进行计数,每次 next之前,此值会递增,当 count <= seen 的时候,说明已经执行了我们预设的数字,此时结束(完成)当前数据流

Join Operators

mergeAll

mergeAllmergeMap 的作用很相似,实际上 mergeAll 内部就调用了 mergeMap,区别点在于,上游的数据流在经过 mergeMap 的时候,会被 mergeMap 内部传入的事先定义好的函数进一步处理,得到新的数据结果,然后再让数据结果继续往下流动;而 mergeAll 只是对上游的数据进行兼容处理,无法做额外的数据处理,实际上,mergeMap 完全可以取代 mergeAll,源码层面,mergeAll 也是调用了 mergeMap,算是进一步的功能封装

概念不好理解,看例子会更清晰些

of(1).pipe(
  map(v => v)
).subscribe(console.log)
// 1
复制代码

上述代码的输出明显,就是 1,现在改一下

of(1).pipe(
  map(v => of(v))
).subscribe(console.log)
// Observable2 {_subscribe: ƒ}
复制代码

这时输出的是一个 Observable,想要取得这个 Observable的值,还得再次 subscribe一次

of(1).pipe(
  map(v => of(v)),
).subscribe(v => v.subscribe(console.log))
复制代码

而如果借助 mergeAll

of(1).pipe(
  map(v => of(v)),
  mergeAll()
).subscribe(console.log)
// 1
复制代码

就不需要进一步 subscribe,本例子还可以使用 mergeMap

of(1).pipe(
  map(v => of(v)),
  mergeMap(v => v)
).subscribe(console.log)
// 1
复制代码

mergeMap 能做得更多,例如,可以改变原数据的值

of(1).pipe(
  map(v => of(v)),
  mergeMap(v => v.pipe(map(n => n *2)))
).subscribe(console.log)
// 2
复制代码

原数据是 1,经过 mergeMap处理后变成了 2

mergeAll 可以接收一个参数,这个参数能够控制同时最多并发订阅的 observable对象数量

of(0, 1, 2, 3).pipe(
  map(v => of(v).pipe(delay(1000))),
  mergeAll()
)
.subscribe(console.log)
复制代码

上述代码将在执行后 1000ms的时候,同时输出 0 1 2 3

of(0, 1, 2, 3).pipe(
  map(v => of(v).pipe(delay(1000))),
  mergeAll(2)
)
.subscribe(console.log)
复制代码

上述代码将在执行后 1000ms的时候,同时输出 0 1,然后再等待 1000ms,再同时输出 2 3

由于 mergeMap 是完全可以取代 mergeAll的,所以 mergeMap 也可以实现

of(0, 1, 2, 3).pipe(
  map(v => of(v).pipe(delay(1000))),
  mergeMap(v => v, 2)
)
.subscribe(console.log)
复制代码

mergeAll 内部调用了 mergeMap,最终都是调用了 mergeInternals

// /rxjs/src/internal/operators/mergeInternals.ts
const outerNext = (value: T) => (active < concurrent ? doInnerSub(value) : buffer.push(value));
复制代码

这里有个判断 active < concurrentactive 初始值为 0,并且每执行一次 doInnerSub(value)就会累计 1concurrentmergeAll 的参数,如果不传这个值就是 Infinity,也就是说,如果 mergeAll不传参,则这个表达式永远执行 doInnerSub(value)

doInnerSub 就是执行数据流订阅的方法,当同时在执行中的 observable 数量大于 concurrent 时,就会将后面的数据流数据先暂存到 buffer中,当有 observable 执行完毕后,则将 active--,并对 buffer里暂存的数据生成 observable 并进行订阅,即执行 doInnerSub

// /rxjs/src/internal/operators/mergeInternals.ts
try {
  active--;
  while (buffer.length && active < concurrent) {
    const bufferedValue = buffer.shift()!;
    if (innerSubScheduler) {
      executeSchedule(subscriber, innerSubScheduler, () => doInnerSub(bufferedValue));
    } else {
      doInnerSub(bufferedValue);
    }
  }
  checkComplete();
} catch (err) {
  subscriber.error(err);
}
复制代码

concatAll

concatAll 实际上是 concat的高阶 Observable操作符,就是说 concatAll 不需要亲自一个个地去接收数据流作为参数,然后再合并了,它只要在 pipe中无脑接收上游数据流就行

// /rxjs/src/internal/observable/concat.ts
export function concatAll<O extends ObservableInput<any>>(): OperatorFunction<O, ObservedValueOf<O>> {
  return mergeAll(1);
}
复制代码

经过前面 mergeMapmergeAll 源码的讲解,concatAll就很清晰了,就是执行了 mergeAll(1),意思就是每次只能同时订阅一个数据流,上一个数据流结束了才能开始下一个

  of(0, 1, 2).pipe(
    map(v => of(v).pipe(delay(1000))),
    concatAll()
  ).subscribe(console.log)
复制代码

上述代码执行后,经过 1s输出 0,再经过 1s输出 1,再经过 1s输出 2,结束

Utility Operators

tap

拦截源上的每个 observable 的数据并对数据进行修改,接着再把这个 observable 继续传递下去

一般而言,operator 在处理完上游的数据之后,会产生一个新的 observable 往下继续传递,但是 tap 不同,它虽然也会修改数据流,但并不会产生新的 observable,它只是修改上游的 observable,然后再把修改后的 observable 传递下去,所以会有 副作用

tap 接收的参数和 observable.subscribe 一样,所以你可以把它看成是一个订阅操作

interval(500).pipe(
  tap(console.log),
  map(n =>  n % 2 ? 'even' : 'odd')
).subscribe(console.log)
// 0
// even
// 1
// add
// ...
复制代码
// /rxjs/src/internal/operators/tap.ts
source.subscribe(
  createOperatorSubscriber(
    subscriber,
    (value) => {
      tapObserver.next?.(value);
      subscriber.next(value);
    },
    () => {
      isUnsub = false;
      tapObserver.complete?.();
      subscriber.complete();
    },
    (err) => {
      isUnsub = false;
      tapObserver.error?.(err);
      subscriber.error(err);
    },
    () => {
      if (isUnsub) {
        tapObserver.unsubscribe?.();
      }
      tapObserver.finalize?.();
    }
  )
);
复制代码

subscriber 执行什么操作, tapObserver 就执行什么操作,并且 tapObserver的操作在 subscriber之前。 subscriber 外部是拿不到的,而外部却能控制 tapObserver,所以算是一种拦截处理

Mathematical and Aggregate Operators

reduce & max

reducescan 很类似,二者都是调用了 scanInternals,只是参数值不同罢了,唯一的区别是,scan 会在每次遍历时将当前累加值 emit出来,而 reduce 只在数据流完成(complete)的时候,才会把最终的累计值 emit 出来,所以使用 reduce的时候,必须要保证数据流能够结束(complete),否则 reduce 不会有结果的

max 类似于 Math.max,用于查找当前数据流中最大的数据值,当然,最大 这个概念实际上取决于你传入的 comparer 函数 利用 reduce

// /rxjs/src/internal/operators/max.ts
export function max<T>(comparer?: (x: T, y: T) => number): MonoTypeOperatorFunction<T> {
  return reduce(isFunction(comparer) ? (x, y) => (comparer(x, y) > 0 ? x : y) : (x, y) => (x > y ? x : y));
}
复制代码
interval(100).pipe(
  map(() => of(Math.random())),
  mergeAll(),
  take(5),
  max((a, b) => a < b ? -1 : 1),
).subscribe(console.log)
// 0.9138789699353005
复制代码

小结

刚开始看 operator 的时候,会感觉很乱,但当摸清楚了套路的时候,就知道哪些是主干哪些是无关紧要的细枝末节了,operatorrxjs 中使用频率最高的 api,了解了其实现原理,有助于我们自行实现定制化的 operator

猜你喜欢

转载自juejin.im/post/7067068740185096199