rxjs

介绍

RxJS是一个异步编程的库,同时它通过observable序列来实现基于事件的编程。它提供了一个核心的类型:Observable,几个辅助类型(Observer,Schedulers,Subjects),受到Array的扩展操作(map,filter,reduce,every等等)启发,允许直接处理异步事件的集合。

ReactiveX结合了Observer模式、Iterator模式和函数式编程和集合来构建一个管理事件序列的理想方式。

在RxJS中管理异步事件的基本概念如下:

  • Observable:代表了一个调用未来值或事件的集合的概念
  • Observer:代表了一个知道如何监听Observable传递过来的值的回调集合
  • Subscription:代表了一个可执行的Observable,主要是用于取消执行
  • Operators:是一个纯函数,允许处理集合与函数式编程风格的操作,比如map、filter、concat、flatMap等
  • Subject:相当于一个EventEmitter,它的唯一的方法是广播一个值或事件给多个Observer
  • Schedulers:是一个集中式调度程序来控制并发性,允许我们在setTimeout或者requestAnimationFrame上进行协调计算

第一个例子

正常注册一个事件监听函数:

var button = document.querySelector('button');
button.addEventListener('click', () => console.log('Clicked!'));

使用RxJS,你可以创建一个observable来代替:

var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
  .subscrible(() => console.log('Clicked!'));

纯粹

使得RxJS变得如此强大的原因是它使用了纯函数,这意味着你的代码很少会发生错误。

正常你不会创建一个纯函数,代码的其他部分可能扰乱你的状态。

var count = 0;
var button = document.querySelector('button');
button.addEventListener('click', () => console.log(`Clicked $(++count) times`));

RxJS将隔离你的状态

var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
  .scan(count => count + 1, 0)
  .subscribe(count => console.log(`Clicked ${count} items`));

scan操作符类似于arrays的reduce操作符。它需要一个回调函数作为一个参数,函数返回的值将作为下次调用时的参数。

Flow

RxJS有一系列的操作符来帮你监控事件将如何流动。

这是一个每秒最多点击一次的程序:

var count = 0;
var rate = 1000;
var lastClick = Date.now() - rate;
var button = document.querySelector('button');
button.addEventListener('click', () => {
  if (Date.now() - lastClick >= rate){
    console.log(`Clicked ${++count} times`);
    lastClick = Date.now();
  }
});

使用RxJS:

var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
  .throttleTime(1000)
  .scan(count => count + 1, 0)
  .subscribe(count => console.log(`Clicked ${count} times`));

另外的控制符还有:filter, delay, debounceTime, take, takeUntil, distinct, distinctUntilChanged等。

你可以使用你的observables来转换值。

这是一个每次点击添加x坐标的程序:

var count = 0;
var rate = 1000;
var lastClick = Date.now() - rate;
var button = document.querySelector('button');
button.addEventListener('click', (event) => {
  if (Date.now() - lastClick >= rate){
    count += event.clientX;
    console.log(count);
    lastClick = Date.now();
  }
})

使用Rxjs:

var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
  .throttleTime(1000)
  .map(event => event.clientX)
  .scan((count, clientX) => count + clientX, 0)
  .subscribe(count => console.log(count));

另外的producing操作符:pluck、pairwise、sample等

Observable

Observables是一个延迟Push(关于Push的概念见后面)操作数据的集合。它们遵从下表:

Single Multiple
Pull Function Iterator
Push Promise Observable

举个例子。下面是一个Observable,当执行subscribed,它将会立即push 1、 2、 3(同步),然后过去一秒后push 4

var observable = Rx.Observable.create(function (observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  setTimeout(() => {
    observer.next(4);
    observer.complete();
  }, 1000); 
});

为了调用Observable,然后看这些值,我们需要对这些数据进行订阅(subscribe)

var observable = Rx.Observable.create(function (observer){
  observer.next(1);
  observer.next(2);
  observer.next(3);
  setTimeout(() => {
    observer.next(4);
    observer.complete();
  })
});

console.log('just before subscribe');
observerble.subscribe({
  next: x => console.log(`got value` + x),
  error: err => console.error('somthing wrong occurred: ' +err),
  complete: () => console.log('done')
});
console.log('just after subscribe');

执行结果如下:

just before subscribe
got value 1
got value 2
got value 3
just after sbuscribe
got value 4
done

Pull和Push

PullPush是关于数据提供者和数据消费者交互的两个不同的协议。

什么是Pull?在Pull系统中,当Consumer收到Producer的数据时,它会自己判断是否接收该数据,Producer自己并不知道数据将交给哪个Consumer。

所有的JavaScript函数都是一个Pull系统。函数是一个数据提供者,调用函数的代码是一个consuming(消费者),它将函数返回的值"pulling"出来。

ES2015介绍了generator functions and iterators (function*),它们是另外一种Pull系统。iterator.next() 是Consumer,它从iterator(Producer)中"pulling"出多个值

Producer Consumer
Pull 被动:当需要时产生数据 主动:决定是否接收数据
Push 主动:自己决定将数据传给谁 被动:响应式接收数据

什么是Push?在Push系统中,Producer决定将数据发往哪个Consumer。Consumer并不知道它自己的值来自哪个Producer

Promise是最常见的一个Push系统。一个Promise(Producer)分发一个结果值给注册的接口(Consumer),与函数不同的是,Promise当遇到值被"push"给callback时,他会保证它传递的对象是正确的。

RxJS介绍了Observables,它是一个新的Push系统。Observable是一个提供多值的Producer,将它们"pushing"给Observers(Consumer)

  • Function:计算并同步调用一个值
  • generator:计算并同步调用多个值
  • Promise:计算后可能(不可能)返回一个值
  • Observable:计算然后同步或异步返回一个或多个值

Observable as generalizations of functions

与主流相反,Observable不像EventEmitters,也不像Promise。在某些情况下,Observable的行为可能像EventEmitters,比如当使用RxJS的Subjects进行多途径传播时,但是大部分的情况它们都是不一样的。

考虑下面的情况:

function foo(){
  console.log('Hello');
  return 42;
}

var x = foo.call();  //  same as foo()
console.log(x);
var y = foo.call();  //  same as foo()
console.log(y)

我们期望出现下面的结果:

"Hello"
42
"Hello"
42

当使用Observables时:

var foo = Rx.Observable.create(function (observer){
  console.log('Hello');
  observer.next(42);
});

foo.subscribe(function (x){
  console.log(x);
});
foo.subscribe(function (y){
  console.log(y);
})

它们有着同样地输出:

"Hello"
42
"Hello"
42

之所以出现这种情况是因为function和Observables都是延迟(lazy)计算的。如果你不调用function,console.log('Hello')这段代码是不会执行的。Observables是同样的,如果你不执行(subscribe)它,代码也不会执行。“calling”和"subscribing"都是一个独立的操作:两个function分别导致两个结果,两个Observale subscribes trigger也会分别导致两个结果。这与EventEmitters截然相反,EventEmitters会共享结果,并且它执行的时候也不会顾忌到底是否有subscribers存在,Observables不会是共享结果,并且也是延迟执行。

Subscribing一个Observable就像调用一个函数一样

一些人要求Observables是异步的,这是不正确的。看下面这个例子:

console.log('before');
console.log(foo.call());
console.log('after');

你将会看到这样的输出:

"before"
"Hello"
42
"after"

使用Observables

console.log('before');
foo.subscribe(function(x) {
  console.log(x);
});
console.log('after');

输出是:

"before"
"Hello"
42
"after"

这证明了foo的订阅是一个完完全全的异步,就像一个函数一样。

Observables可以同步或异步地传递一个值

Observable和function的不同是什么呢?随之时间的流逝,Observables可以“返回”多个值,函数是不可以的。你不可以这么做:

function foo(){
  console.log('Hello');
  return 42;
  return 100;  //  不会执行到这儿
}

函数只能返回一次,Observables可以做到返回多次:

var foo = Rx.Observable.create(function (observer){
  console.log('Hello');
  observer.next(42);
  observer.next(100);  //  "return another value"
  observer.next(200);  //  "return" yet another
});

console.log('before');
foo.subscribe(function (x){
  console.log(x);
});
console.log('after');

同步输出:

"before"
"Hello"
42
100
200
"after"

你也可以异步返回:

var foo = Rx.Observable.create(function (observer){
  console.log('Hello');
  observer.next(42);
  observer.next(100);
  observer.next(200);
  setTimeout(() => {
    observer.next(300);  //  异步
  }, 1000);
});

console.log('before');
foo.subscribe(function(x){
  console.log(x);
});
console.log('after');

输出:

"before"
"Hello"
42
100
200
"after"
300

结论:

  • func.call()表示“同步给我一个数据”
  • observable.subscribe()表示“给我任何数量的值,同步或者异步”

解析一个Observable

Observables使用Rx.Observable.create或者一个构造器创建(create),使用Observer来监听(subscribed),执行(execute)是通过投递一个next/error/complete来通知其他的Observer,然后按照各自的意愿(disposed)来执行。在一个Observable实例中,这四个方面都是通过编码实现的,但是这些可能与其他的类型相关,比如Obsrever和Subscription。

Observable的核心点:

  • Creating Observables
  • Subscribing to Observables
  • Executing the Observable
  • Disposing Observables

创建一个Observables

Rx.Observable.create是Observable构造器的一个别名,他需要一个参数:一个subscribe函数

下面的例子创建一个Observable,它的作用是每秒钟输出字符串'hi':

var observable = Rx.Observable.create(function subscrite(observer){
  var id = setInterval(() => {
    observer.next('hi')
  }, 1000);
});

Observables可以使用create创建,但是我们经常使用creation operators,诸如from,interval等。

在上面的例子中,subscribe函数是描述Observable最重要的一部分,让我们来看看subscribing是什么意思。

subscribing to Observables

Observable的observable可以被订阅(subscribed),就像这样:

observable.subscribe(x => console.log(x));

observable.scribe和Observable.create(function subscribe(observer) {...})中的subscribe有着相同的名字并不是巧合。在库中,它们是不同的,但是在实际的用途中你可以在逻辑上把他们想成相同的。

同样的Observable被多个Observers监听时,它们是不共享的。

Subscribing一个Observable像调用一个函数一样,当一个数据被传递时提供一个回调

这个addEventListener/removeEventListener这样的API完全不一样。observable.subscribe作为一个给定的观察者,在Observable中并没有像listener一样被注册。Observable甚至不需要维护一系列的Observers。

Executing observables

代码Observable.create(function subscribe(observer) {...})代表了一个"Observable execution",它将仅仅在每个Observer的subscribes的延迟计算中。随着时间的推移,将产生多个结果,同步或者异步。

Observable可以传递的有三种类型:

  • "Next" notification:传递一个数值,诸如Number、String、Object等
  • “Error” notification:传递一个js异常
  • "Complete" notification:什么值都不传递

Next notifications是最重要的也是最常见的类型:它们表示一个实际数据被送到Observer。在Observable Execute执行期间Error和Complete最多会发生一次。

下面的语法是在Observable Grammar or Contract中最好的表达:

next*(error|complete)?

在一个Observable Execute中,0或多个Next notifications可能被传递。如果有error或者Complete被传递,剩下的next将不会被传递。

下面是Observable execute传递3个Next notifications的例子:

var observable = Rx.Observable.create(function subscribe(observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();
})

下面的例子中,Next notification 4不会被传递:

var observable = Rx.Observable.create(function subscribe(observer){
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();
  observer.next(4);  //  不会被执行
})

用tru/catch代码快包裹起来是个好主意:

var observable = Rx.Observable.create(function subscribe(observer) {
  try {
    observer.next(1);
    observer.next(2);
    observer.next(3);
    observer.complete();
  } catch (err) {
    observer.error(err); // delivers an error if it caught one
  }
});

处理(Disposing)Observable Executions

Observable Executing的个数可能是无限个,Observer中应该处理有限个next,所以我们需要一个API来停止execution。因为execution在每个Observer中都是独立的,一旦Observer完成接收值,它必须有一个方法来停止executing。

当 observable.subscribe 被调用,Observer将被附加到一个新创建的Observable execution中,这次调用将返回一个对象,即Subscription:

var subscription = observable.subscribe(x => console.log(x));

Subscription代表了一个进行中的executing,它有一个最小的API允许你取消execution。可以在这里阅读更多有关于 Subscription type here 的东西。使用 subscription.unsubscribe() 你可以取消正在进行的execution:

var observable = Rx.Observable.from([10, 20, 30]);
var subscription = observable.subscribe(x => console.log(x));
//  Later:
subscription.unsubscribe();

当我们使用create()创建一个Observable时,我们必须定义execution怎么处理资源。你可以通过返回一个自定义的 unsubscribe 函数来实现该步骤。

var observable = Rx.Observable.create(function subscribe(observer){
  var intervalID = setInterval(() => {
    observer.next('hi')
  });

  return function unsubscribe(){
    clearInterval(intervalID);
  }
})

然后这样来调用:

function subscribe(observer) {
  var intervalID = setInterval(() => {
    observer.next('hi');
  }, 1000);

  return function unsubscribe() {
    clearInterval(intervalID);
  };
}

var unsubscribe = subscribe({next: (x) => console.log(x)});

// Later:
unsubscribe(); // dispose the resources

Observer

什么是Observer?一个Observer是Observable传递过来的数据的customer。Observers是一个简单的一些列的回调,next、error、和 complete用来传递数据。下面的例子展现了一个典型的Observer对象:

var observer = {
  next: x => console.log('Observable got a next value: ' + x),
  error: err => console.log('Observable got and error: ' + err),
  complete: () => console.log('Observable got a complete notification')
};

为了使用Observalbe,提供了一个subscribe:

observable.subscribe(observer)

你也可以提供部分回调:

var observer = {
  next: x => console.log('Observer got a next value: ' + x),
  error: err => console.error('Observer got an error: ' + err),
};

当你订阅(subscribing)一个Observable时,你也许仅仅只提供一个函数作为参数:

observable.subscribe(x => console.log('Observer got a next value: ' + x));

在observable.subscribe的内部,他将使用第一个回调创建一个Observer对象作为一个next handler。所有的callback类型都可能被提供:

observable.subscribe(
  x => console.log('Observer got a next value: ' + x),
  err => console.error('Observer got an error: ' + err),
  () => console.log('Observer got a complete notification')
);

Subscription

什么是Subscription?一个Subscription代表了一个一次性的资源,通常表示的是一个Observable execution。一个Subscription有一个重要的方法,unsubscribe,它不需要参数,仅仅是处理subscription的资源。在之前的RxJS版本中,Subscription被称作"Disposable"。

var observable = Rx.Observable.interval(1000);
var subscription = observable.subscribe(x => console.log(x));
// Later:
// This cancels the ongoing Observable execution which
// was started by calling subscribe with an Observer.
subscription.unsubscribe();

一个Subscription实质上是一个unsubscribe()函数,用来释放资源或者取消一个Observable executions。

Subscriptions也可以放在一起,这样会导致使用一个unsubscribe()将取消多个Observable executions。你可以这么做:

var observable1 = Rx.Observable.interval(400);
var observable2 = Rx.Observable.interval(300);

var subscription = observable1.subscribe(x => console.log('first: ' + x));
var childSubscription = observable2.subscribe(x => console.log('second: ' + x));

subscription.add(childSubscription);

setTimeout(() => {
  // Unsubscribes BOTH subscription and childSubscription
  subscription.unsubscribe();
}, 1000);

当执行时,我们将看到如下输出:

second: 0
first: 0
second: 1
first: 1
second: 2

Subscriptions有一个remove(otherSubscription)方法,用来移除关联的Subscirption

Subject

什么是Subject?一个RxJS Subject是一个特殊类型的Observable,它允许值可以多路广播给多个Observers。普通的Observables是单路广播(每个subscribed Observer拥有自己独立的Observable execution),Subjects是多路广播。

一个Subject像一个Observable,但是可以多路广播给Observers。Subjects像Eventmitters:它们维持许多注册过的监听器。

每个subject是一个Observable。给定一个Subject,你可以通过提供一个Observer来订阅(subscribe)它,然后开始正常的接收值。从Observer的角度来看,他不能告知Observer的Observable execution到底是来自一个不同的单路传播的Observable,还是来自Subject。

在Subject的内部,subscribe并没有调用一个新的execute去传递数据。它只是简单的注册Observers列表中的一个Observer,类似于addListener的使用。

每个subject是一个Observer。他是拥有next(v),error(e)和complete()方法的对象。为了给Subject一个新值,只需要调用 next(theValue),他讲多路传播给注册过的Observer。

在下面的例子中,我们在Subject中注册了两个Observers,我们传递一些值给Subject:

var subject = new Rx.Subject();

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

subject.next(1);
subject.next(2);

输出:

observerA: 1
observerB: 1
observerA: 2
observerB: 2

因为Subject同时也是一个Observer,这意味着你应该提供一个Subject作为Observable的subscribe的参数,像这样:

var subject = new Rx.Subject();

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

var observable = Rx.Observable.from([1, 2, 3]);

observable.subscribe(subject);  // You can subscribe providing a Subject

执行如下:

observerA: 1
observerB: 1
observerA: 2
observerB: 2
observerA: 3
observerB: 3

在上面的处理中,我们本质上仅仅是通过Subject将一个单路广播的Observable execution变为多路广播的。这个演示展示了Subjects是怎样将单路广播变为多路广播的。

这里有几个特殊的Subject类型:BehaviorSubject、ReplaySubject和AsyncSubject。

Multicasted Observables

一个"multicasted Observable"的实现是通过Subject的多个订阅(subscribers)来实现的,然而一个"unicast Observable"仅仅只通知一个单一的Observer。

在后台,multicast是这样操作的:Observers订阅(subscribe)一个相关的Subject,Subject订阅一个Observable源。

var source = Rx.Observable.from([1, 2, 3]);
var subject = new Rx.Subject();
var multicasted = source.multicast(subject);

// These are, under the hood, `subject.subscribe({...})`:
multicasted.subscribe({
  next: (v) => console.log('observerAa: ' + v)
});
muticasted.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

// This is, under the hood, `source.subscribe(subject)`:
muticasted.connect();

Reference counting

手动的调用connect()来处理Subscription是很麻烦的。通常,我们希望当第一个Observer到达时,能够自动connect,当最后一个Observer被移除时,自动取消shared execution。

看看下面这些订阅发生时的列表:

  1. 第一个Observer订阅multicasted Observable
  2. multicasted observable连接
  3. next value 0被传递给第一个Observer
  4. 第二个Observer订阅multicasted Observable
  5. next value 1被传递给第一个Observer
  6. next value 1被传递给第二个Observer
  7. 第一个Observer解除监听
  8. next value2被传递给第二个Observer
  9. 第二个Observer解除监听
  10. 与multicasted observable连接的Observable解除连接

看下面的代码:

var source = Rx.Observable.interval(500);
var subject = new Rx.Subject();
var multicasted = source.multicast(subject);
var subscription1, subscription2, subscriptionConnect;

subscription1 = multicasted.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
// We should call `connect()` here, because the first
// subscriber to `multicasted` is interested in consuming values
subscriptionConnect = multicasted.connect();

setTimeout(() => {
  subscription2 = multicasted.subscribe({
    next: v => console.log('observerB: ' + v)
  });
}, 600);

setTimeout(() => {
  subscrption1.unscribe();
}, 1200);

// We should unsubscribe the shared Observable execution here,
// because `multicasted` would have no more subscribers after this
setTimeout(() => {
  subscription2.unsubscribe();
  subscriptionConnect.unsubscribe(); // for the shared Observable execution
}, 2000);

如果我们希望避免一遍遍地调用connect(),我们可以使用ConnectableObservable的refCount()方法(reference counting),它返回一个Observable来跟踪有多少个订阅者(subscribers)。当订阅者从0增加到1时,它将自动调用connect(),只有当订阅者从1变为0时,它才会disconnect。

看下面的例子:

var source = Rx.Observable.interval(500);
var subject = new Rx.Subject();
var refCounted = source.multicast(subject).refCount();
var subscrption1, subscription2, subscriptionConnect;


// This calls `connect()`, because
// it is the first subscriber to `refCounted`
console.log('observerA subscribed');
subscription1 = refCounted.subscribe({
  next: (v) => console.log('observerA: ' + v);
});

setTimeout(() => {
  console.log('observerB subscribed');
  subscription2 = refCounted.subscribe({
    next: (v) => console.log('observerA: ' + v)
  });
}, 600);

setTimeout(() => {
  console.log('observerA unsubscribed');
  subscription1.unsubscribe();
}, 1200);

// This is when the shared Observable execution will stop, because
// `refCounted` would have no more subscribers after this
setTimeout(() => {
  console.log('observerB unsubscribed');
  subscription2.unsubscribe();
}, 2000);

执行结果:

observerA subscribed
observerA: 0
observerB subscribed
observerA: 1
observerB: 1
observerA unsubscribed
observerB: 2
observerB unsubscribed

refCount()方法仅存在ConnectableObservable中,它返回一个Observable,而不是另外的ConnectableObservable。

BehaviorSubject

Subjects的一种变形是BehaviorSubject,它有一个"the current value" 的概念。它存储了consumer最后一次执行时的value,每当一个Observer订阅时,它都会立即从BehaviorSubject接收一个"current value"。

例子:

var subject = new Rx.BehaviorSubject(0);  //  0 is the inital value

subject.subscribe({
  next: v => console.log('observerA: ' + v)
});

subject.next(1);
subject.next(2);

subject.subscribe({
  next: v = console.log('observerB: ' + v)
});

subject.next(3);

输出:

observerA: 0
observerA: 1
observerA: 2
observerB: 2
observerA: 3
observerB: 3

ReplaySubject

功能和它的名字一样:

var subject = new Rx.ReplaySubject(3);  // buffer 3 values for new subscribers

subject.subscribe({
  next: v => console.log('observerA: ' + v)
});

subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);

subject.subscribe({
  next: v => console.log('observerB: ' + v)
});

subject.next(5);

输出:

observerA: 1
observerA: 2
observerA: 3
observerA: 4
observerB: 2
observerB: 3
observerB: 4
observerA: 5
observerB: 5

你还可以指定一个以毫秒为单位的窗口事时间,除了buffer size之外,决定记录的值可以重复(时间内)。

var subject = new Rx.ReplaySubject(100, 500);

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});

var i = 1;
setInterval(() => subject.next(i++), 200);

setTimeout(() => {
  subject.subscribe({
    next: v => console.log('observerB: ' + v)
  });
}, 1000)

下面的输出中,第二个Observer在最后500ms内得到的数值为3、 4、 5:

observerA: 1
observerA: 2
observerA: 3
observerA: 4
observerA: 5
observerB: 3
observerB: 4
observerB: 5
observerA: 6
observerB: 6
...

AsyncSubject

AsyncSubject表示只有最后一个Observable execution的值会被发送给observers,仅仅发生在执行完成时

var subject = new Rx.AsyncSubject();

subject.subscrbe({
  next: v => console.log('onbserverA: ' + v)
});

subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);

subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

subject.next(5);
subject.complete();

输出:

observerA: 5
observerB: 5

AsyncSubject类似于一个last() operator,他等待complete通知来传递一个唯一的值。

Opeartors

RxJS最有用的一部分就是operators,即使Observable是最基础的。Operators最基本的要点是允许复杂的代码变得简单化。

什么是Operators?

Opeartors是Obsrevable的方法,就像map(),filter(),merge()等。当它被调用时,它们并不改变已经存在的Observable,而是返回一个基于第一个Observable上新的Observable。

一个Operator本质上是一个纯函数,它接收一个Observable,基于其上返回一个新的Observable。在下面的例子中,我们创建了一个自定义的operator方法:

function multiplayByTen(input){
  var output = Rx.Observable.create(function subscribe(observer){
    input.subscribe({
      next: v => observer.next(10 * v),
      error: err => observer.error(err),
      complete: () => observer.complete()
    });
  });
return output;
}

var input = Rx.Observable.from([1, 2, 3 ,4]);
var output = multiplayByTen(input);
output.subscribe(x => console.log(x));

输出为:

10
20
30
40

注意订阅(subscribe)的输出将导致输入的Observable可观测的变化。我们称这个为"operator subscription chain"。

Instance opeartors versus static operators

什么是instance operator?最常见的情况是当你引用一个opeartors时,我们假定实现了一个operator,它是Observable实例的一个方法。例如,如果multiplayByTen operator变成一个官方的operator,它看起来会是这样:

Rx.Observable.prototype.multiplyByTen = function multiplyByTen(){
  var input = this;
  return Rx.subscrible.create(function subscribe(observer){
    input.subccribe({
      next: (v) => observer.next(10 * v),
      error: (err) => observer.error(err),
      complete: () => observer.complete()
    });
  });
}

Instance operators是一个实例运算符,我们使用它来推断可观测的输入。

注意,input observable不再是一个函数参数:

var observable = Rx.Observable.from([1, 2, 3, 4]).multiplyByTen();
observable.subscribe(x => console.log(x));

什么是static operator?除了instance operators之外,static operators是直接附加在Observable类上的方法。一个static operator使用内部的this进行操作,但是并不完全依赖它的参数。

static operators是附着在Observable类上的纯函数,通常用于创建Observable

最常见的static operators类型是Create Operators,他不是将一个Observable改变成另外一个Observable,它们简单的获得一个non-Observable参数,比如number,然后create一个新的Observable。

一个典型的例子是使用interval函数。它获取一个数值(不是一个Observable)作为输入参数,然后输出一个Observable:

var observable = Rx.Observable.interval(1000 /* number of milliseconds */)

创建一个creation operaor的另外一个例子是create,就是我们之前一直在使用的例子。 all static creation operators here

然而,static operators也许和普通的creation性质不同。一些Combination Operator也许是静态的,比如merge、combineLatest、concat等。将这些作为静态是有意义的,因为它们把multiple Observales作为输入,不只是一个,比如:

var observable1 = Rx.Observable.interval(1000);
var observable2 = Rx.Observable.interval(400);

var merged = Rx.Observable.merge(observable1, observable2);

Marble diagrams

为了解释operators是如何工作的,光是文本解释是不够的。许多operators和时间有关,它们可能会延迟执行,例如,throttle等。图标往往能够比文字更多表达清楚。Marble Diagrams能够可视化的表现出operators是如何工作的,包括输入的Observable(s),operator和它的参数,以及输出的Observable

在一个marble diagram中,随着时间的流逝,它会描述值("marbles")在Observable execution上传递。

你可以在下面看到marble diagram的解析:

Paste_Image.png
  • 时间从左往右流动,代表input Observable的execution
  • 这些代表Observable传递传来的值
  • 这个竖线表示"complete" notification,它表明Observable已经成功完成了。
  • 这个方框表示input Observable的operator(上图)产生出的output Observable(下图)。方框内的文字表示转变的属性。
  • 这个Observable是调用operator产生的
  • 这个X代表output Observable发出的错误,说明因为某些原因而异常终止。

在这个网站的站点,我们会广泛的使用marble diagrams去解释operators是如何工作的。它们也许在其他的地方也很有用,比如单元测试等。

选择一个operator

你需要为你的程序选择一个适当的operator吗?先从下面的列表选择一个:

  • 我已经有了一个Observable
  • 我想改变每个传递的值
    • 让它成为一个固定(constant)的值
    • 通过公式计算出来的值
      • 你应该使用map
  • 我想选择每个传递值的属性
  • 我想查看每个被传递的值,但是不影响它们
    • 你应该使用do
  • 我想过滤某些值
    • 基于一个自定义的逻辑

更多内容参考官网:Choose an operator

operators的分类

参考官网:Categories of operators

Scheduler

什么是Scheduler?当一个subscription开始工作或者notifications被传递,scheduler就会开始调图。它包含三个组件。

  • 一个Scheduler是一个数据结构(data structure)。它知道如何基于优先级或者其它标准进行存储,执行队列任务
  • 一个Scheduler是一个执行上下文(execution context)。它表示task在哪个地方,什么时候执行()
  • 一个Scheduler是一个(虚拟(virtual))时钟。它基于scheduler上的getter方法now(),提出了一个"时间(time)"的概念。任务被安排在一个特殊的调度器中,它会遵守给它的时间。

看下面例子中,我们使用之前已经写过的例子,同步传递数值1、2、 3,然后使用observerOn操作符来指定异步调度:

var observable = Rx.Observable.create(function (observer) {
    observer.next(1);
    observer.next(2);
    observer.next(3);
    observer.complete();
})
.observerOn(Rx.Scheduler.async);

console.log('just before subscribe');
observable.subscribe({
    next: x => console.log('got value ' + x),
    error: err => console.error('something wrong occurred: ' + err),
    complete: () => console.log('done')
});
console.log('just after subscribe');

输出:

just before subscribe
just after subscribe
got value 1
got value 2
got value 3
done

注意got value这个语句实在 just after subscribe只有打印输出的,这和我们看到的代码顺序不一致。这时因为 observerOn(Rx.Scheduler.async)在Observable.create和最后一个Observer之间引入了一个代理的Observer。让我们重新为一些标识符取名,以便让他们之间有着明显的差别:

var observable = Rx.Observable.create(function (proxyObserver) {
    proxyObserver.next(1);
    proxyObserver.next(2);
    proxyObserver.next(3);
    proxyObserver.complete();
})
    .observeOn(Rx.Scheduler.async);

var finalObserver = {
    next: x => console.log('got value ' + x),
    error: err => console.error('something wrong occurred: ' + err),
    complete: () => console.log('done')
};

console.log('just before subscribe');
observable.subscribe(finalObserver);
console.log('just after subscribe');

proxyObserver在observeOn(Rx.Scheduler.async)中创建,它的next(val)方法大概像下面这样:

var proxyObserver = {
  next: (val) => {
    Rx.Scheduler.async.schedule(
      (x) => finalObserver.next(x),
      0 /* delay */,
      val /* will be the x for the function above */
    );
  },

  // ...
}

这有点儿像setTimeout或者setInterval是异步调度操作,即使给定的delay为0。按照惯例,在JS中,setTimeout(fn, 0)知道运行fn函数的时机最早是下一次循环队列初。这也说明了为什么 got value 1是最后运行的。

可以给Scheduler的schedule传递一个延时(delay)参数,它可以让Scheduler内部的时钟去延时到指定时间。Scheduler的时钟和真实的时钟没有任何关系。它更类似于延时,而不是运行指定的时间。

Scheduler类型

异步Scheduler只是RxJS提供的一种Scheduler。通过使用Scheduler的静态方法可以创建下面的类型

Scheduler Purpose
null 不使用Scheduler, notifications将会被同步和递归地交付给Observer。使用这个来进行常量操作或者尾部递归操作
Rx.Scheduler.queue Schedules on a queue in the current event frame (trampoline scheduler). Use this for iteration operations.
Rx.Scheduler.asap Schedules on the micro task queue, which uses the fastest transport mechanism available, either Node.js' process.nextTick() or Web Worker MessageChannel or setTimeout or others. Use this for asynchronous conversions.
Rx.Scheduler.async Schedules work with setInterval. Use this for time-based operations.

使用Schedulers

Using Schedulers

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容