Edited at

[RxJS] Subjectを実装してみる

業務でRxJSのSubjectを使っていたのですが、簡単なものだったらできるかもしれないと思い実装してみました。

とりあえず、簡単にオブジェクトを返すSubject関数を作ってみます。


Subject.js

const Subject = () => {

return {
subscriptions: [],
next: function(a) {
this.subscriptions.forEach(fn => fn(a));
},
subscribe: function(fn) {
this.subscriptions.push(fn);
},
}
}

オブジェクトを返すことでnextsubscribeが呼べます。returnで返される自分自身のsubscriptionsにアクセスしたいのでアロー関数ではなくfunctionを使っています。アロー関数と普通のfunctionの違いは現在勉強中です。ちゃんと動いているかテストしてみます。

(追記 2018/12/2: this使わない実装を最後に追加しました。)


Subject.js

const Subject = () => { /* 省略 */}

const sub = Subject();
sub.subscribe(a => console.log(a));
sub.next(1);
sub.next(2);
sub.next('abc');
sub.next(5 + 8);


node Subject.jsとやると以下の通り出力するのでとりあえず動いてます。

1

2
abc
13

サブスクリプションを追加しても動くことを確認してみます。


Subject.js

const Subject = () => { /* 省略 */}

const sub = Subject();
sub.subscribe(a => console.log(a));
sub.next(1);
sub.next(2);
sub.next('abc');
sub.subscribe((a) => console.log(`another subscriber: ${a}`));
sub.next(5 + 8);


以下の通り出力されます。

1

2
abc
13
another subscriber: 13

次にunsubscribeを実装します。実際のSubjectではSubscriptionインスタンスを返すのですが、今回はunsubscribeを持ったオブジェクトを返すことにします。


Subject.js

const Subject = () => {

return {
count: 0,
subscriptions: [],
next: function(a) {
this.subscriptions.forEach(sub => sub.fn(a))
},
subscribe: function(fn) {
var id = this.count;
this.subscriptions.push({
id,
fn,
})
this.count += 1;

return {
unsubscribe: () => {
this.subscriptions = this.subscriptions.filter(sub => sub.id !== id);
}
}
},
}
}


どのサブスクリプションを除けばいいかを把握するためにサブスクライブされたときに、subscriptions配列にidを格納します。テストしてみます。


Subject.js

const Subject = () => { /* 省略 */ }

const sub = Subject();
const subscription = sub.subscribe(a => console.log(a));
sub.next(1);
subscription.unsubscribe();
sub.next('This should not be printed');


以下の通りunsubscribeされたあとのnextの値は出力されないので正しく動いてそうです。

1

テストを増やしてきちんと動いてるかを確認します。


Subject.js

const Subject = () => { /* 省略 */ }

const sub = Subject();
const subscription1 = sub.subscribe(a => console.log(a));
sub.next(1);
sub.next(2);

const subscription2 = sub.subscribe(a => console.log(`Second subscription: ${a}`));
sub.next('X');
sub.next('Y');

subscription1.unsubscribe();

sub.next('Subscription2 should only work');
sub.next(10);
subscription2.unsubscribe();

sub.next('END');


1

2
X
Second subscription: X
Y
Second subscription: Y
Second subscription: Subscription2 should only work
Second subscription: 10

きちんと動いてるようです。

これで基本的な機能をもったSubjectは作れたはずです。もし、間違いがあったらコメントで教えていただけると幸いです。

追記 2018/12/2

thisを使わない実装です。ここではクロージャを使用してSubject内に定義された変数にアクセスしています。


Subject.js

const Subject = () => {

let count = 0;
let subscriptions = [];

const next = (val) => {
subscriptions.forEach(sub => sub.fn(val));
}
const subscribe = (fn) => {
const id = count;
subscriptions.push({
id,
fn,
});
count += 1;

const unsubscribe = () => {
subscriptions = subscriptions.filter(sub => sub.id !== id);
}

return {
unsubscribe,
};
}

return {
next,
subscribe,
}
}