業務でRxJSのSubjectを使っていたのですが、簡単なものだったらできるかもしれないと思い実装してみました。
とりあえず、簡単にオブジェクトを返すSubject関数を作ってみます。
const Subject = () => {
return {
subscriptions: [],
next: function(a) {
this.subscriptions.forEach(fn => fn(a));
},
subscribe: function(fn) {
this.subscriptions.push(fn);
},
}
}
オブジェクトを返すことでnext
やsubscribe
が呼べます。returnで返される自分自身のsubscriptions
にアクセスしたいのでアロー関数ではなくfunctionを使っています。アロー関数と普通のfunctionの違いは現在勉強中です。ちゃんと動いているかテストしてみます。
(追記 2018/12/2: this
使わない実装を最後に追加しました。)
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
サブスクリプションを追加しても動くことを確認してみます。
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
を持ったオブジェクトを返すことにします。
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を格納します。テストしてみます。
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
テストを増やしてきちんと動いてるかを確認します。
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
内に定義された変数にアクセスしています。
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,
}
}