ReactiveExtensions
RxJS
Rx
RxJSDay 21

RxJS の Observable で EventEmitter を扱う

More than 1 year has passed since last update.

この記事は bouzuya's RxJS Advent Calendar 2015 の 21 日目かつ RxJS Advent Calendar 2015 の 21 日目です。

はじめに

いまさらながら Event を Observable で操作します。Observable.fromEvent とその簡単な例を書きます。

通常なら DOM の Event で使うところですが、ここまで Node.js で試してきたので EventEmitter を使います。どちらも Observable.fromEvent で対応できます。

Observable.fromEvent

EventEmitter の例

まず Observable を使わない EventEmitter の例を挙げます。

import { EventEmitter } from 'events';

const emitter = new EventEmitter();

emitter.on('data', value => console.log(value));

emitter.emit('data', 'Hello, Event!');
// Hello, Event!

EventEmitter.prototype.on で event listener を登録します。ここでは流れてくる 'data' event の値を表示するようにしています。EventEmitter.prototype.emit'data' event として 'Hello, Event!' を流しています。

Observable の例

次に Observable を使った EventEmitter の例を挙げます。

import { EventEmitter } from 'events';
import { Observable } from 'rx';

const emitter = new EventEmitter();

Observable
  .fromEvent(emitter, 'data') // fromEvent(element, 'cilcked')
  .subscribe(value => console.log(value));

emitter.emit('data', 'Hello, Event!');
// Hello, Event!

Observable.fromEventemitter'data' event から Observable instance をつくっています。Observable.prototype.subscribe は説明不要でしょう。

自然に読みかえられると思います。

Observable の良い点

前述の Observable の例では 1 行増えただけで良い点が分かりづらいので、もうすこしいくつかの例を挙げます。

流れてくる文字のほかにそれを mask ('*' でつぶす) した文字を表示するようにします。愚直に書くとこんな感じでしょうか。

import { EventEmitter } from 'events';
import { Observable } from 'rx';

const emitter = new EventEmitter();

Observable
  .fromEvent(emitter, 'data')
  .subscribe(value => console.log(value));

Observable
  .fromEvent(emitter, 'data')
  .map(s => s.split('').map(() => '*').join(''))
  .subscribe(value => console.log(value));

emitter.emit('data', 'H');
emitter.emit('data', 'He');
emitter.emit('data', 'Hell');
emitter.emit('data', 'Hello, Event!');
// H
// *
// He
// **
// Hell
// ****
// Hello, Event!
// *************

まず Observable の Operator を使えるのは良い点です。mapfilter で event を変換したり不要なものを取り除いたり……この Advent Calendar で 10 日以上をかけても紹介しきれない Operator を使えます。

また interface が統一されるのも良い点です。今回は EventEmitter ですが、それが element になっても、同様に Observable として扱えるのは良い点でしょう。

ほかの良い点を示すためにもう一つ例を挙げます。

import { EventEmitter } from 'events';
import { Observable } from 'rx';

const emitter = new EventEmitter();

const event$ = Observable.fromEvent(emitter, 'data');
const masked$ = event$.map((s) => s.split('').map(() => '*').join(''));

Observable
  .merge(event$, masked$)
  .subscribe(value => console.log(value));

emitter.emit('data', 'H');
emitter.emit('data', 'He');
emitter.emit('data', 'Hell');
emitter.emit('data', 'Hello, Event!');
// H
// *
// He
// **
// Hell
// ****
// Hello, Event!
// *************

実行結果は同じですが先ほどより随分と重複コードを減らしています。また mask された値を流す Observable に適切な名前をつけています。このように柔軟に分割できることで個別に Observable の動きを test することもできるでしょう。

おわりに

実はまだ全体の 2/5 くらいで、続きを書いているのですが、長くなるので分割します。明日は Observable.fromEvent (Observable.fromEventPattern) の実装を読んでいこうと思います。