使用几个小例子来理解rxjs操作符

import { Observable, BehaviorSubject,ReplaySubject,AsyncSubject, Subject, combineLatest, zip, interval, of, from, fromEventPattern,range,defer,fromEvent,merge,zip,race, EMPTY, merge,Subject,asapScheduler  } from 'rxjs';
import { map,filter ,mapTo,take,concat,switchAll,withLatestFrom,startWith, switchMap,concatMap,  scan,takeUntil,buffer,bufferCount,bufferWhen,bufferTime,debounceTime,distinct,distinctUntilChanged,
  delay,delayWhen,catchError,retry,retryWhen,finalize,pluck,toArray,partition,multicast,refCount,tap,share
} from 'rxjs/operators'; // merge


/*订阅*/
// var subject$ = new Subject();   //实例化一个Subject对象
// subject$.next(1);                  //向接受者发送一个消息流
//
//
// subject$.subscribe({
//   next: (value) => console.log('observerA: ' + value)  //接受者A订阅消息,获取消息流中的数据
// });
// subject$.subscribe({
//   next: (value) => console.log('observerB: ' + value)  //接受者B订阅消息,获取消息流中的数据
// });


// var subject = new BehaviorSubject(0); //声明一个BehaviorSubject对象
// subject.next(1);           //发送一个数据流
// subject.next(2);           //再发送一个数据流
// subject.subscribe({
//   next: (v) => console.log('observerA: ' + v)  //接受者A订阅消息
// });
// subject.subscribe({
//   next: (v) => console.log('observerB: ' + v)  //接受者B订阅消息
// });
// subject.next(3);    //再发送一个数据流


// const source$ = new Observable(observer => {  try {
//   observer.next(1)
//   observer.next(2)
//   throw new Error('there is an exception')
//   observer.complete()
// } catch (e) {
//   observer.error(e)
// }
// })
//
// source$.subscribe(
//   item => console.log(item),
//   e => console.log(e),
//   () => console.log('complete')
// )

// const observer = {
//   next: item => console.log(item),
//   error: e => console.log(e),
//   complete: () => console.log('complete')
// }
//
// source$.subscribe(observer)

/*操作符*/
// const source$ = interval(1000).pipe(
//   map(x => x * x)
// )
// source$.subscribe(x => console.log(x))

/*of*/
// const source$ = of(1, 2, 3)
// source$.subscribe(
//     item => console.log(item),
//   e => console.log(e),
//   () => console.log('complete')
// )

/*from*/

// const source$ = from([1, 2, 3])
// source$.subscribe(
//     item => console.log(item),
//   e => console.log(e),
//   () => console.log('complete')
// )


/*
* fromEvent 方法
* 用 DOM 事件创建 Observable,第一个参数为 DOM 对象,第二个参数为事件名称。具体示例见前面 RxJS 入门章节的一个简单例子。
*
* fromEventPattern 方法
* 将添加事件处理器、删除事件处理器的 API 转化为 Observable。
* */

// function addClickHandler (handler) {  document.addEventListener('click', handler)
// }
// function removeClickHandler (handler) {  document.removeEventListener('click', handler)
// }
//
// fromEventPattern(
//   addClickHandler,
//   removeClickHandler
// ).subscribe(x => console.log(x))


/*interval、timer
* interval 和 JS 中的 setInterval 类似,参数为间隔时间,下面的代码每隔 1000 ms 会发出一个递增的整数。*
* timer 则可以接收两个参数,第一个参数为发出第一个值需要等待的时间,
* 第二个参数为之后的间隔时间。第一个参数可以是数字,也可以是一个 Date 对象,第二个参数可省。
*
* */
// interval(1000).subscribe(val => console.log(val));// 0// 1// 2// ...


/*range
* 操作符 of 产生较少的数据时可以直接写如 of(1, 2, 3),但是如果是 100 个呢?这时我们可以使用 range 操作符。\
* */
// range(1, 100).subscribe(val => console.log(val)) // 产生 1 到 100 的正整数

/*
* empty、throwError、never
* empty 是创建一个立即完结的 Observable,
* throwError 是创建一个抛出错误的Observable,
* never 则是创建一个什么也不做的 Observable(不完结、不吐出数据、不抛出错误)。
* 这三个操作符单独用时没有什么意义,主要用来与其他操作符进行组合。
* 目前官方不推荐使用 empty 和 never 方法,而是推荐使用常量 EMPTY 和 NEVER(注意不是方法,已经是一个 Observable 对象了)
**/


/*
8 defer
8 defer 创建的 Observable 只有在订阅时才会去创建我们真正想要操作的 Observable。
defer 延迟了创建 Observable,而又有一个 Observable 方便我们去订阅,这样也就推迟了占用资源。
*/
// defer(() => ajax(ajaxUrl))
// 只有订阅了才会去发送 ajax 请求。

/*
* pipeable 操作符
* 之前版本的 RxJS 各种操作符都挂载到了全局 Observable 对象上,可以这样链式调用:
* 其实也很好理解,pipe 就是管道的意思,数据流通过操作符处理,流出然后交给下一个操作符。
*/
// const source$ = from([1, 2, 3]);
// // source$.filter(x => x % 2 === 0).map(x => x * 2)
// // 现在需要这样使用:
// const pipeSource$ = source$.pipe(  filter(x => x % 2 === 0),  map(x => x * 2)
// )
// pipeSource$.subscribe(val=>console.log(val)); // 4



/*
* 几个类似数组方法的基础操作符
* map、filter 和数组的 map、filter 方法类似,scan 则是和 reduce 方法类似,mapTo 是将所有发出的数据映射到一个给定的值。
*/

// fromEvent(document, 'click').pipe(take(2),
//   mapTo('Hi')
// ).subscribe(x => console.log(x),null,()=> console.log('complete'))


/*
* 一些过滤的操作符
* take 是从数据流中选取最先发出的若干数据
* takeLast 是从数据流中选取最后发出的若干数据
* takeUntil 是从数据流中选取直到发生某种情况前发出的若干数据
* first 是获得满足判断条件的第一个数据
* last 是获得满足判断条件的最后一个数据
* skip 是从数据流中忽略最先发出的若干数据
* skipLast 是从数据流中忽略最后发出的若干数据
*/
// 使用了 take(3),表示只取 3 个数据,Observable 就进入完结状态。
// interval(1000).pipe(
//   take(3)
// ).subscribe(
//   x => console.log(x),  null,
//   () => console.log('complete')
// )// 0// 1// 2// 'complete'

// 点击按钮停止发射;
// interval(1000).pipe(
//   takeUntil(fromEvent(document.querySelector('#btn'), 'click'))
// ).subscribe(
//   x => { document.querySelector('#time').textContent = x + 1 },  null,
//   () => console.log('complete')
// )




/*合并类操作符*/

/*
1)concat、merge
* concat、merge 都是用来把多个 Observable 合并成一个,
* 但是 concat 要等上一个 Observable 对象 complete 之后才会去订阅第二个 Observable 对象获取数据并把数据传给下游,
* 而 merge 时同时处理多个 Observable。使用方式如下:
 */

// 前后执行
// interval(500).pipe(
//   take(3),
//   concat(interval(300).pipe(take(6)))
// ).subscribe(x => console.log(x))  // 0,1,2,0,1,2,3,4,5

// 并行   from 'rxjs/operators'
// interval(500).pipe(
//   take(3),
//   merge(interval(300).pipe(take(6)))
// ).subscribe(x => console.log(x))  // 例如:0,0,1,1,2,2,3,4,5     0,1,2,1,3,2,4,5,6

// 注意最新的官方文档和RxJS v5.x 到 6 的更新指南中指出不推荐使用 merge、concat、combineLatest、race、zip 这些操作符方法,而是推荐使用对应的静态方法。
// 将上面的 merge 改成从 rxjs 中导入,使用方式变成了合并多个 Observable,而不是一个 Observable 与其他 Observable 合并。
// 并行  from 'rxjs'
// merge(
//   interval(500).pipe(take(3)),
//   interval(300).pipe(take(6))
// ).subscribe(x => console.log(x)) // 例如:0,0,1,1,2,2,3,4,5     0,1,2,1,3,2,4,5,6


/*concatAll、mergeAll、switchAll*/
/*
* 用来将高阶的 Observable 对象压平成一阶的 Observable,和 loadash 中压平数组的 flatten 方法类似。
* concatAll 会对内部的 Observable 对象做 concat 操作,和 concat 操作符类似,如果前一个内部 Observable 没有完结,
* 那么 concatAll 不会订阅下一个内部 Observable,mergeAll 则是同时处理。switchAll 比较特殊一些,
* 它总是切换到最新的内部 Observable 对象获取数据。上游高阶 Observable 产生一个新的内部 Observable 时,
* switchAll 就会立即订阅最新的内部 Observable,退订之前的,这也就是 ‘switch’ 的含义。
 */

// interval(1500).pipe(
//   take(2),
//   map(x => interval(1000).pipe(
//     map(y => x + ':' + y),
//     take(2))
//   ),
//   switchAll()
// ).subscribe(console.log)// 0:0// 1:0// 1:1



// 内部第一个 Observable 对象的第二个数据还没来得及发出,第二个 Observable 对象就产生了。
// 3)concatMap、mergeMap、switchMap
// switch() 用于取消前一个订阅,并切换至新的订阅,
// 从上面的例子我们也可以看到高阶 Observable 常常是由 map 操作符将每个数据映射为 Observable 产生的,
// 而我们订阅的时候需要将其压平为一阶 Observable,而就是要先使用 map 操作符再使用 concatAll 或 mergeAll 或 switchAll 这些操作符中的一个。RxJS 中提供了对应的更简洁的 API。使用的效果可以用下面的公式表示:
// concatMap = map + concatAll
// mergeMap = map + mergeAll
// switchMap = map + switchAll
 //上面的功能可以用switchMap改写:
// interval(1500).pipe(
//   take(2),
//   switchMap(x => interval(1000).pipe(
//     map(y => x + ':' + y),
//     take(2))
//   ),
// ).subscribe(console.log)// 0:0// 1:0// 1:1





/*
* zip 有拉链的意思,这个操作符和拉链的相似之处在于数据一定是一一对应的
 */
// 使用 zip 当有数据流吐出数据很快,而有数据流发出值很慢时,要小心数据积压的问题。
// 这时快的数据流已经发出了很多数据,由于对应的数据还没发出,RxJS 只能保存数据,快的数据流不断地发出数据,积压的数据越来越多,消耗的内存也会越来越大。
// const source$ = interval(500).pipe(take(3))
// const newest$ = interval(300).pipe(take(6))
// const add = (x, y) => x + y
//
// zip(source$, newest$).subscribe(x => console.log(x)) // [0, 0]// [1, 1]// [2, 2]//

/*
* combineLatest 与 zip 不同,
* 只要其他的 Observable 已经发出过值就行,顾名思义,就是与其他 Observable 最近发出的值结合
* */

// const source$ = interval(500).pipe(take(3));
// const newest$ = interval(300).pipe(take(6))
//
// combineLatest(source$, newest$).subscribe(x => console.log(x)) // [0, 0]// [0, 1]// [0, 2]// [1, 2]// [1, 3]// [2, 3]// [2, 4]// [2, 5]


/*
* withLatestFrom
* 没有静态方法,只有操作符方法,前面的方法所有 Observable 地位是平等的,而这个方法是使用这个操作符的 Observable 起到了主导作用,即只有它发出值才会进行合并产生数据发出给下游。
* */
// const source$ = interval(500).pipe(take(3));
// const newest$ = interval(300).pipe(take(6))
//
// source$.pipe(
//   withLatestFrom(newest$)
// ).subscribe(x => console.log(x))// [0, 0]// [1, 2]// [2, 4]

// source 发出 0 时,newest 最接下来发出的值为 0,结合为 [0, 0] 发出
// source 发出 1,此时 newest 最接下来发出的值为 2,结合为 [1, 2] 发出
// source 发出 2,此时 newest 最新发出的值为 4,结合为 [2, 4] 发出
// source 完结,整个 Observable 完结

/*
* startWith、forkJoin、race
* startWith 是在 Observable 的一开始加入初始数据,同步立即发送,常用来提供初始状态。
 */
// const source$ = fromEvent(document.querySelector('#btn'), 'click');
// let number = 0;
// const fakeRequest = x => {  return new Promise((resolve, reject) => {
//   setTimeout(() => {
//     console.log(x) // initData  // MouseEvent{}
//     resolve(number++)
//   }, 1000)
// })
// }

// source$.pipe(
//   startWith('initData'),
//   switchMap(x => {
//     const v = from(fakeRequest(x));
//     return v;
//   })
// ).subscribe(x => document.querySelector('#number').innerHTML = x)
// // 这里通过 startWith 操作符获取了页面的初始数据,之后通过点击按钮获取更新数据。
// // forkJoin 只有静态方法形式,类似 Promise.all ,它会等内部所有 Observable 都完结之后,将所有 Observable 对象最后发出来的最后一个数据合并成 Observable。


/*
* race 操作符产生的 Observable 会完全镜像最先吐出数据的 Observable。
* */
// const obs1 = interval(1000).pipe(mapTo('fast one'));
// const obs2 = interval(3000).pipe(mapTo('medium one'));
// const obs3 = interval(5000).pipe(mapTo('slow one'));
//
// race(obs3, obs1, obs2).subscribe(
//     winner => console.log(winner)
//   );// result:// a series of 'fast one'


/*
* 页面上有一个 p 标签存放一个状态,初始为 0,有两个按钮,一个按钮点击后这个状态增加 1,另一个按钮点击后这个状态减少 1。
*这两个按钮的点击事件我们都可以建立响应式数据流,可以使用 mapTo(1) 和 mapTo(-1) 分别表示点击后增加 1 和减少 1。
* 我们可以使用 EMPTY 创建一个空的数据流来表示这个状态,用 startWith 设定初始值。
* 然后 merge 这两个点击的数据流,但是这还有一个问题,点击事件的数据流需要与表示状态的数据流进行逻辑计算,发出最终的状态,
* 我们才能去订阅这个最终的数据流来更改页面的显示。而这种累计计算的方法,可以用 scan 操作符来实现。最终实现如下:
* */
// const addButton = document.getElementById('addButton');
// const minusButton = document.getElementById('minusButton');
// const state = document.getElementById('state');
// const addClick$ = fromEvent(addButton, 'click').pipe(mapTo(1));
// const minusClick$ = fromEvent(minusButton, 'click').pipe(mapTo(-1));
//
// merge(
//   EMPTY.pipe(startWith(0)),
//   addClick$,
//   minusClick$)
//   .pipe(
//     scan((origin, next) => { // 两个参数,一个是原始流,一个是当前流
//       console.log('origin:'+origin);
//       console.log('next:'+next);
//       return origin + next
//     })
//   ).subscribe(item => {
//   state.textContent = item
// })
// //0 1 1 1 1 1 -1 -1     //0是默认的初始值, 1是addClick$点击时候发出的流, -1是minusClick$点击时候发出的流
// //  1 2 3 4 5  4  3     // 由于是scan返回的是(origin + next),所以得到的结果是上面的值依次相加



/*简单拖拽*/
// const eleDrag = document.querySelector('#drag');
// const eleBody = document.body;
//
// const mouseDown$ = fromEvent(eleDrag, 'mousedown');
// const mouseMove$ = fromEvent(eleBody, 'mousemove');
// const mouseUp$ = fromEvent(eleBody, 'mouseup');
//
// mouseDown$.pipe(
//   // concatMap(mouseDownEvent => mouseMove$.pipe(
//   //   map(mouseMoveEvent => ({
//   //     left: mouseMoveEvent.clientX - mouseDownEvent.offsetX,
//   //     top: mouseMoveEvent.clientY - mouseDownEvent.offsetY
//   //   })),
//   //   takeUntil(mouseUp$)
//   // ))
//   /* 上面注释掉的方法和下面的方法都可以 */
//   concatMap(mouseDownEvent => mouseMove$.pipe(
//     takeUntil(mouseUp$)
//   )),
//   withLatestFrom(mouseDown$, (move, down) => ({
//     left: move.clientX - down.offsetX,
//     top: move.clientY - down.offsetY
//   }))
// ).subscribe(position => {
//   eleDrag.style.left = position.left + 'px'
//   eleDrag.style.top = position.top + 'px'
// })



/*
* 缓存
把上游的多个数据缓存起来,当时机合适时再把汇聚的数据传给下游。
1)buffer、bufferTime、bufferCount、bufferWhen、bufferToggle
对于 buffer 这一组操作符,数据汇聚的形式就是数组。
buffer 接收一个 Observable 作为 notifier,当 notifier 发出数据时,将 缓存的数据传给下游。
* */

// interval(300).pipe(
//   take(30),
//   buffer(interval(1000))
// ).subscribe(
//   x => console.log(x)
// )
// // [0, 1, 2]
// // [3, 4, 5]
// // [6, 7, 8]
// // [9, 10, 11, 12]
// ..
//
// bufferTime 是用时间来控制时机,上面可以改成 bufferTime(1000)
// bufferCount 是用数量来控制时机,如 3 个一组,bufferCount(3)
// bufferWhen 接收一个叫做 closeSelector 的参数,它应该返回一个 Observable。通过这个 Observable 来控制缓存。这个函数没有参数。下面的方法等价于前面的 buffer:.

// interval(300).pipe(
//   take(30),
//   bufferWhen(() => {
//     return interval(1000)
//   })
// ).subscribe(
//   x => console.log(x)
// )


/*bufferToggle 和 buffer 的不同是可以不断地控制缓存窗口的开和关,一个参数是一个 Observable,称为 opening,
第二个参数是称为 closeSelector 的一个函数。这个函数的参数是 opening 产生的数据。
前一个参数用来控制缓存的开始时间,后一个控制缓存的结束。与 bufferWhen 相比,它的 closeSelector 可以接收参数,控制性更强。
我们可以使用 buffer 来做事件的过滤,下面的代码只有 500ms 内连续点击两次以上才会输出 ‘success’ 。
*/
// fromEvent(document.querySelector('#btn'), 'click').pipe(
//   bufferTime(500),
//   filter(arr => {return arr.length >= 2}) // 每点击一次arr的lengt加1   [MouseEvent, MouseEvent]
// ).subscribe(
//   x => console.log('success')
// )


/*window、windowTime、windowCount、windowWhen、windowToggle*/
/*
* 与前面的 buffer 类似,不过 window 缓存数据汇聚的形式是 Observable,因此形成了高阶 Observable。
debounceTime、throttleTime
类似 lodash 的 debounce 和 throttle,用来降低事件的触发频率。
我们做搜索时,常常要对输入进行 debounce 来减少请求频率。
* */

// fromEvent(document.querySelector('#searchInput'), 'input').pipe(
//   debounceTime(300),
//   map(e => e.target.value)
// ).subscribe(
//   input => document.querySelector('#text').textContent = input
//   // 发送请求
// )


/*
distinct、distinctUntilChanged
distinct 操作符可以用来去重,将上游重复的数据过滤掉。
 */

// of(1, 1, 2, 2, 2, 1, 2, 3, 4, 3, 2, 1).pipe(
//   // zip(interval(1000)),
//   distinct()
// ).subscribe(x => console.log(x))
// // distinct()的话上面的代码只会输出 1, 2, 3, 4
// // distinctUntilChanged()的话:上面的代码只会输出 1, 2,1,2,3,4,3,2,1

// distinct 操作符还可以接收一个 keySelector 的函数作为参数,这是官网的一个 typescript 的例子: 去重
// interface Person {
//   age: number,
//   name: string
// }
//
// of<Person>(
//   { age: 1, name: '张三' },
//   { age: 2, name: '李四' },
//   { age: 3, name: '张三' },
//   { age: 4, name: '李四' },
// ).pipe(
//   distinct((p:Person) => p.name),
// ).subscribe(x => console.log(x))
//
// // { age: 4, name: 'Foo' }
// // { age: 7, name: 'Bar' }

// distinctUntilChanged 也是过滤重复数据,但是只会与上一次发出的元素比较。这个操作符比 distinct 更常用。
// distinct 要与之前发出的不重复的值进行比较,因此要在内部存储这些值,要小心内存泄漏,而 distinctUntilChanged 只用保存上一个的值。



/*
dalay、delayWhen
用来延迟上游 Observable 数据的发出。

delay 可以接受一个数字(单位默认为 ms)或者 date 对象作为延迟控制。
 */
// const clicks = fromEvent(document, 'click')
// const delayedClicks = clicks.pipe(delay(1000)) // 所有点击事件延迟 1 秒
// delayedClicks.subscribe(x => console.log(x))


/*
我们前面介绍过 bufferWhen,dalayWhen 也带有 when,在 RxJS 中,这种操作符它接收的参数都是 Observable Factory,即一个返回 Observable 对象的回调函数,用这个 Observable 来进行控制。

每个 click 都延迟 0 至 5 秒之间的任意一个时间:
 */
// const clicks = fromEvent(document, 'click')
// const delayedClicks = clicks.pipe(
//   delayWhen(event => interval(Math.random() * 5000)),
// )
// delayedClicks.subscribe(x => console.log(x))



/*
异常错误处理
异常处理的难点:

try/catch 只支持同步
回调函数容易形成回调地狱,而且每个回调函数的最开始都要判断是否存在错误
Promise 不能重试,而且不强制异常被捕获
对错误处理的处理可以分为两类,即恢复(recover)和重试(retry)。

恢复是虽然发生了错误但是让程序继续运行下去。重试,是认为这个错误是临时的,重试尝试发生错误的操作。实际中往往配合使用,因为一般重试是由次数限制的,当尝试超过这个限制时,我们应该使用恢复的方法让程序继续下去。(遇到错误重试)
 */

//catchError
// catchError 用来在管道中捕获上游传递过来的错误。
// interval(1000).pipe(
//   take(6),
//   map(x => {
//     if (x === 4) {
//       throw new Error('unlucky number 4')
//     } else {
//       return x
//     }
//   }),
//   catchError(err => of(63)) // 当报错时用63代替
// ).subscribe(x => console.log(x))
//catchError 中的回调函数返回了一个 Observable,当捕获到上游的错误时,调用这个函数,返回的 Observable 中发出的数据会传递给下游。因此上面当 x 为4 时发生了错误,会用 8 来替换。


// catchError 中的回调函数除了接收错误对象为参数外,还有第二个参数 caught$ 表示上游的 Observable 对象。如果回调函数返回这个 Observable 对象,就会进行重试。
// interval(1000).pipe(
//   take(6),
//   map(x => {
//     if (x === 4) {
//       throw new Error('unlucky number 4')
//     } else {
//       return x
//     }
//   }),
//   catchError((err, caught$) => {console.log(caught$);return caught$}), // 返回上游的 Observable 对象,进行重试
//   take(20)
// ).subscribe(x => console.log(x))
// 这个代码会依次输出 5 次 0, 1, 2, 3。


/*
retry
retry 可以接收一个整数作为参数,表示重试次数,如果是负数或者没有传参,会无限次重试。重试实际上就是退订再重新订阅。(遇到错误重试)
 */
// interval(1000).pipe(
//   take(6),
//   map(x => {
//     if (x === 4) {
//       throw new Error('unlucky number 4')
//     } else {
//       return x
//     }
//   }),
//   retry(5) // 重试 5 次
// ).subscribe(x => console.log(x))

//在实际开发中,如果是代码原因造成的错误,重试没有意义,如果是因为外部资源导致的异常错误适合重试,如用户网络或者服务器偶尔不稳定的时候。

/*
retryWhen
和前面带 when 的操作符一样,retryWhen 操作符接收一个返回 Observable 的回调函数,用这个 Observable 来控制重试的节奏。当这个 Observable 发出一个数据时就会进行一次重试,它完结时 retryWhen 返回的 Observable 也立即完结。
 */
// interval(1000).pipe(
//   take(6),
//   map(x => {
//     if (x === 4) {
//       throw new Error('unlucky number 4')
//     } else {
//       return x
//     }
//   }),
//   retryWhen(err$ => err$.pipe(
//     delay(3000),
//     take(5))
//   ) // 延迟 1 秒后重试,重试 5 次
// ).subscribe(x => console.log(x))


// retryWhen 的可定制性非常高,不仅可以实现延迟定制,还可以实现 retry 的控制重试次数。在实践中,这种重试频率固定的方法还不够好,如果之前的重试失败,之后重试成功的几率也不高。Angular 官网介绍了一个 Exponential backoff 的方法。将每次重试的延迟时间控制为指数级增长
/*???????????????????????????????????????????????????????????*/
// function backoff(maxTries, ms) {
//   return pipe(
//     retryWhen(attempts => range(1, maxTries)
//       .pipe(
//         zip(attempts, (i) => i),
//         map(i => i * i),
//         mergeMap(i =>  timer(i * ms))
//       )
//     )
//   );
// }
//
// ajax('/api/endpoint')
//   .pipe(backoff(3, 250))
//   .subscribe(data => handleData(data));
//
// function handleData(data) {
//   // ...
// }
/*???????????????????????????????????????????????????????????*/

/*
finalize

返回上游数据流的镜像 Observable,当上游的 Observable 完结或出错时调用传给它的函数,不影响数据流。
 */

// interval(1000).pipe(
//   take(6),
//   map(x => {
//     if (x === 4) {
//       throw new Error('unlucky number 4')
//     } else {
//       return x
//     }
//   }),
//   finalize(() => console.log('finally'))
// ).subscribe(x => console.log('a'))



/*
tap 操作符
我们可以使用 tap 操作符来进行调试。

拦截源 Observable 的每一次发送,执行一个函数,返回源 Observable 的镜像 Observable。

这个 API 有助于我们对 Observable 的值进行验证(debug)和执行一个会带来副作用的函数,而不会影响源 Observable。如我们用鼠标进行 canvas 绘图,鼠标按下是开始画图,鼠标松开即停止。我们需要在 mousedown 的时候进行 moveTo,否则这次画的会和上次画的连在一起。我们应该把这个会带来副作用过程放在 tap 操作符的函数中,这样才不会影响原来的数据流。

tap 操作符和订阅并不相同,tap 返回的 Observable 如果没有被订阅,tap 中产生副作用的函数并不会执行。
 */


/*
repeat:repeat 用来重复上游 Observable
 */

/*
pluck 类似 lodash 的方法 pluck,提取对象的嵌套属性的值。
 */
// const click$ = fromEvent(document, 'click')
// const tagName$ = click$.pipe(pluck('target', 'tagName'))
// tagName$.subscribe(x => console.log(x))
// 等价于:
// click$.pipe(map(e => e.target.tagName))

/*
toArray:将发出的数据汇聚为数组
 */
// interval(1000).pipe(
//   take(3),
//   toArray()
// ).subscribe(x => console.log(x))

/*
partition:将上游的 Observable 分为两个,一个 Observable 的数据是符合判定的数据,另一个时不符合判定的数据。
 */
// const part$ = interval(1000).pipe(
//   take(6),
//   partition(x => x % 2 === 0)
// )
//
// part$[0].subscribe(x => console.log(x)) // 0, 2, 4
// part$[1].subscribe(x => console.log(x)) // 1, 3, 5



/*
* getSuggestList 是一个发送 ajax 请求的方法,返回 promise,我们使用 from 来将其转化为 Observable。
为了优化请求,首先 e.target.value 是空字符串时不应该发送请求,然后可以使用 debounceTime 减少触发频率,也可以使用 distinctUntilChanged 操作符来表示只有与上次不同时才去发送请求。我们还可以在 API 失败时重试 3 次。
* */
// const input = document.querySelector('#search');
// const suggestList = document.querySelector('#suggest-list');
// const input$ = fromEvent(input, 'input');
// input$.pipe(
//   filter(e => e.target.value.length > 1),
//   debounceTime(1000),
//   distinctUntilChanged()
// ).subscribe((val)=>console.log(val.target.value))
//
// function getSuggestList(val){
//   console.log(val)
//   return new Promise(function(resolve, reject){
//     //异步操作
//     setTimeout(()=>{
//       resolve([
//         {
//           name:'张三1',
//           age:21
//         },{
//           name:'张三2',
//           age:22
//         },{
//           name:'张三3',
//           age:23
//         },{
//           name:'张三4',
//           age:24
//         },{
//           name:'张三5',
//           age:25
//         }
//       ]);
//     },2000)
//   });
// }


/*多播
* 前面的例子都是只有一个订阅者的情况,实际上当然可以有多个订阅者,这就是多播(multicast),即一个数据流的内容被多个 Observable 订阅。
*
* Hot Observable 和 Cold Observable
* */
// 每次订阅都重新生产一份数据流
// const source$ = interval(1000).pipe(
//   take(3)
// )
//
// source$.subscribe(x => console.log('Observer 1: ' + x))
//
// setTimeout(() => {
//   source$.subscribe(x => console.log('Observer 2: ' + x))
// }, 1000)

/*
Subject
为了防止每次订阅都重新生产一份数据流,我们可以使用中间人,让这个中间人去订阅源数据流,观察者都去订阅这个中间人。这个中间人能去订阅数据流,所以是个 Observer,又能被观察者订阅,所以也是 Observable。我们可以自己实现一个这样的中间人:
 */
// const subject = {
//   observers: [],
//   subscribe: function (observer) {
//     this.observers.push(observer)
//   },
//   next: function (value) {
//     this.observers.forEach(o => o.next(value))
//   },
//   error: function (error) {
//     this.observers.forEach(o => o.error(error))
//   },
//   complete: function () {
//     this.observers.forEach(o => o.complete())
//   }
// }
// //v这个 subject 拥有 Observer 的 next、error、complete 方法,每次被观察者订阅时都会在内部保存这个观察者。当接收到源数据流的数据时,会把数据发送给每一个观察者。
// const source$ = interval(1000).pipe(
//   map(x => Math.floor(Math.random() * 10)),
//   take(3)
// )
//
// const observerA = {
//   next: x => console.log('Observer A: ' + x),
//   error: null,
//   complete: () => console.log('Observer A completed')
// }
// const observerB = {
//   next: x => console.log('Observer B: ' + x),
//   error: null,
//   complete: () => console.log('Observer B completed')
// }
//
// source$.subscribe(subject)
// subject.subscribe(observerA)
// setTimeout(() => {
//   subject.subscribe(observerB)
// }, 1000)

//这时我们发现两个观察者接收到的是同一份数据,ObserverB 由于延迟一秒订阅,所以少接收到一个数据。将我们自己实现的 subject 换成 RxJS 中的 Subject,效果相同:
// const subject = new Subject()
// subject.subscribe(x => console.log('Observer A: ' + x))
// setTimeout(() => {
//   subject.subscribe( x => console.log('Observer B: ' + x))
// }, 500)
//
//
// subject.next(1)
// setTimeout(() => {
//   subject.next(2)
// }, 1000)
// 总结一下,Subject 既是 Observable 又是 Observer,它会对内部的 observers 清单进行组播(multicast)。
// Subject 的错误处理
// 在 RxJS 5 中,如果 Subject 的某个下游数据流产生了错误异常,而又没有被 Observer 处理,那这个 Subject 的其他 Observer 都会失败。但是在 RxJS 6 中不会如此。
//
// 在 v6 的这个例子 中,ObserverA 没有对错误进行处理,但是并不影响 ObserverB,而在 v5 这个demo中因为 ObserverA 没有对错误进行处理,使得 ObserverB 终止了。很明显 v6 的这种处理更符合直觉。


/*
BehaviorSubject:BehaviorSubject 需要在实例化时给定一个初始值,如果没有默认是 undefined,每次订阅时都会发出最新的状态,即使已经错过数据的发送时间。
 */
// const observerA = {
//   next: x => console.log('Observer A: ' + x)
// }
// const observerB = {
//   next: x => console.log('Observer B: ' + x)
// }
//
// const subject = new BehaviorSubject(0)
//
// subject.subscribe(observerA) // Observer A: 0
//
// subject.next(1) // Observer A: 1
// subject.next(2) // Observer A: 2
// subject.next(3) // Observer A: 3
//
// setTimeout(() => {
//   subject.subscribe(observerB) // Observer B: 3
// }, 500)


/*
ReplaySubject 表示重放,在新的观察者订阅时重新发送原来的数据,可以通过参数指定重放最后几个数据。
 */
// const observerA = {
//   next: x => console.log('Observer A: ' + x)
// }
// const observerB = {
//   next: x => console.log('Observer B: ' + x)
// }
//
// const subject = new ReplaySubject(2) // 重放最后两个
//
// subject.subscribe(observerA)
//
// subject.next(1) // Observer A: 1
// subject.next(2) // Observer A: 2
// subject.next(3) // Observer A: 3
// subject.complete()
//
// setTimeout(() => {
//   subject.subscribe(observerB)
//   // Observer B: 2
//   // Observer B: 3
// }, 500)

// 这里我们可以看到,即使 subject 完结后再去订阅依然可以重放最后两个数据。
// ReplaySubject(1) 和前面的 BehaviorSubject 是不一样的,首先后者可以提供默认数据,而前者不行,其次前者在 subject 终结后再去订阅依然可以得到最近发出的数据而后者不行。



/*
AsyncSubject 有点类似 operator last,会在 subject 完结后送出最后一个值。
 */
// const observerA = {
//   next: x => console.log('Observer A: ' + x)
// }
// const observerB = {
//   next: x => console.log('Observer B: ' + x)
// }
// const subject = new AsyncSubject()
//
// subject.next(1)
// subject.next(2)
// subject.next(3)
// subject.complete()
//
// subject.subscribe(observerA)
// // Observer A: 3
// setTimeout(() => {
//   subject.subscribe(observerB)
//   // Observer B: 3
// }, 500)
// observerA 即使早就订阅了,但是并不会响应前面的 next,完结后才接收到最后一个值 3。


/*
前面我们写的 Subject 需要去订阅源数据流和被观察者订阅,写起来比较繁琐,我们可以借助操作符来实现
 */
/*
multicast:使用方式如下,接收一个 subject 或者 subject factory。这个操作符返回了一个 connectable 的 Observable。等到执行 connect() 才会用真的 subject 订阅 source,并开始发送数据,如果没有 connect,Observable 是不会执行的
 */
// const source = interval(1000).pipe(
//   map(x => Math.floor(Math.random() * 10)),
//   take(3),
//   multicast(new Subject)
// )
//
// const observerA = {
//   next: x => console.log('Observer A: ' + x),
//   error: null,
//   complete: () => console.log('Observer A completed')
// }
// const observerB = {
//   next: x => console.log('Observer B: ' + x),
//   error: null,
//   complete: () => console.log('Observer B completed')
// }
//
// source.subscribe(observerA) // subject.subscribe(observerA)
//
// source.connect() // source.subscribe(subject)
//
// setTimeout(() => {
//   source.subscribe(observerB) // subject.subscribe(observerB)
// }, 1000)


/*
refCount
上面使用了 multicast,但是还是有些麻烦,还需要去手动 connect。这时我们可以再搭配 refCount 操作符创建只要有订阅就会自动 connect 的 Observable。只需要去掉 connect 方法调用,在 multicast 后面再加一个 refCount 操作符。
 */

// multicast(new Subject),
//   refCount()
//refCount 其实就是自动计数的意思,当 Observer 数量大于 1 时,subject 订阅上游数据流,减少为 0 时退订上游数据流。

// multicast selector 参数

// multicast 第一个参数除了是一个 subject,还可以是一个 subject factory,即返回 subject 的函数。这时使用了不同的中间人,每个观察者订阅时都重新生产数据,适用于退订了上游之后再次订阅的场景。
//
// multicast 还可以接收可选的第二个参数,称为 selector 参数。它可以使用上游数据流任意多次,而不会重复订阅上游的数据。当使用了这个参数时,multicast 不会返回 connectable Observable,而是这个参数(回调函数)返回的 Observable。selecetor 回调函数有一个参数,通常叫做 shared,即 multicast 第一个参数所代表的 subject 对象

// const selector = shared => {
//   return shared.pipe(concat(of('done')))
// }
// const source = interval(1000).pipe(
//   take(3),
//   multicast(new Subject, selector)
// )
//
// const observerA = {
//   next: x => console.log('Observer A: ' + x),
//   error: null,
//   complete: () => console.log('Observer A completed')
// }
// const observerB = {
//   next: x => console.log('Observer B: ' + x),
//   error: null,
//   complete: () => console.log('Observer B completed')
// }
//
// source.subscribe(observerA)
// setTimeout(() => {
//   source.subscribe(observerB)
// }, 5000)
// observerB 订阅时会调用 selector 函数,subject 即shared 已经完结,但是 concat 依然会在这个 Observable 后面加上 'done'。


/*
可以利用 selector 处理 “三角关系”的数据流,如有一个 tick$ 数据流,对其进行 delay(500) 操作后的下游 delayTick$, 一个由它们合并得到的 mergeTick$,这时就形成了三角关系。delayTick$ 和 mergeTick$ 都订阅了 tick$。
 */
// const tick$ = interval(1000).pipe(
//   take(1),
//   tap(x => console.log('source: ' + x))
// )
//
// const delayTick$ = tick$.pipe(
//   delay(500)
// )
//
// const mergeTick$ = merge(tick$, delayTick$).subscribe(x => console.log('observer: ' + x))
// source: 0
// observer: 0
// source: 0
// observer: 0
// 从上面的结果我们可以验证,tick$ 被订阅了两次。


// 我们可以使用 selector 函数来使其只订阅一次,将上面的过程移到 selector 函数内即可。
// const source$ = interval(1000).pipe(
//   take(1),
//   tap(x => console.log('source: ' + x))
// )
//
// const result$ = source$.pipe(
//   multicast(new Subject(), shared => {
//     const tick$ = shared
//     const delayTick$ = tick$.pipe(delay(500))
//     const mergeTick$ = merge(tick$, delayTick$)
//     return mergeTick$
//   })
// )
//
// result$.subscribe(x => console.log('observer: ' + x))
//这时只会输出一次 'source: 0'。

/*publish 是 multicast 的一种简写方式,效果等同于如下:*/
// function publish (selector) {
//   if (selector) {
//     return multicast(() => new Subject(), selector)
//   } else {
//     return multicast(new Subject())
//   }
// }

/*
share 是 multicast 和 refCount 的简写,share() 等同于在 pipe 中先调用了 multicast(() => new Subject()),再调用了 refCount()。
 */
// const source = interval(1000).pipe(
//   take(3),
//   share()
// )
//
// const observerA = {
//   next: x => console.log('Observer A: ' + x),
//   error: null,
//   complete: () => console.log('Observer A completed')
// }
// const observerB = {
//   next: x => console.log('Observer B: ' + x),
//   error: null,
//   complete: () => console.log('Observer B completed')
// }
//
// source.subscribe(observerA)
// setTimeout(() => {
//   source.subscribe(observerB)
// }, 5000)
// 由于 share 是调用了 subject 工厂函数,而不是一个 subject 对象,因此 observerB 订阅时可以重新获取数据。

/*同前面的 publish,只不过使用的不是普通 Subject,而是对应的 AsyncSubject、BehaviorSubject、ReplaySubject。*/



/*Scheduler*/

/*Scheduler(调度器)用于控制数据流中数据的推送节奏。*/
// const source$ = range(1, 3, asapScheduler)
//
// console.log('before subscribe')
// source$.subscribe(x => console.log(x))
// console.log('subscribed')
发布了112 篇原创文章 · 获赞 149 · 访问量 55万+

猜你喜欢

转载自blog.csdn.net/l284969634/article/details/88644940