Help us understand the problem. What is going on with this article?

RxJS の Observable で EventEmitter を扱う

More than 5 years have 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) の実装を読んでいこうと思います。

bouzuya
ぼく、ぼうずや。なさけはひとのためならず。たのしいはせいぎ。
http://bouzuya.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away