[RxJS] Subjectを実装してみる

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

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

```js:Subject.js
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`使わない実装を最後に追加しました。)

```js: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
```
サブスクリプションを追加しても動くことを確認してみます。

```js: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`を持ったオブジェクトを返すことにします。

```js: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を格納します。テストしてみます。

```js: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
```

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

```js: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`を使わない実装です。ここでは[クロージャ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures)を使用して`Subject`内に定義された変数にアクセスしています。

```js: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,
  }
}
```