RxJS で WEB-API のレスポンス結果を表示する例


[RxJS](https://github.com/Reactive-Extensions/RxJS) は Reactive Extensions の JavaScript 版です。タイトルどおり、WEB-API にアクセスした結果を取得して、(非同期で)画面に表示するというのをやってみたので、そのメモです。

> 俗にいうデータバインディングも RxJS を利用すれば簡単に実装できます。

## デモ

デモはこちら（GitHub Pages）。
⇒ http://hkusu.github.io/rxjs-babel-browserify-gulp-demo/

Qiita API(v1)を利用して、入力キーワードに関連する投稿を取得して表示しています。

![スクリーンショット 2015-06-05 11.11.27.png](https://qiita-image-store.s3.amazonaws.com/0/35733/473a6b8a-c52a-9332-ce11-16543a20d8f1.png "スクリーンショット 2015-06-05 11.11.27.png")

## フロントエンドの環境

今回の環境については下記に書きましたが、かいつまむと ES6 + Browserify の環境です。
⇒ [ES6 で書く環境（Babel + Browserify + gulp 編）＆ RxJS と jQuery の導入例](http://qiita.com/hkusu/items/95bb9278a5ab70c26663)

なので次のように npm コマンドで RxJS および jQuery を導入しています。

```shell:
$ npm install --save-dev rx
$ npm install --save-dev jquery
```

package.json は次のとおり(主要なもののみ抜粋)です。

```json:package.json
{
〜
  "devDependencies": {
    "rx": "^2.5.3",
    "jquery": "^2.1.4",
    "babelify": "^6.1.1",
    "browserify": "^10.2.1",
    "gulp": "^3.8.11",
    "gulp-webserver": "^0.9.1",
    "vinyl-source-stream": "^1.1.0"
  },
  "scripts": {
    "build": "browserify --debug --transform babelify es6/app.es6 --outfile js/bundle.js"
  },
〜
}
```

## 実装

### HTML

DOMの要素としては入力欄(`input`タグ)と結果表示欄(`UL`タグ)のみです。また HTML から読み込んでいる JavaScript ファイルは Browserify でビルド済みのもの(今回の例でいうと`bundle.js`)です。

> この `bundle.js` に RxJS や jQuery 等のライブラリのコードも含んでいます。

```html:index.html
<!DOCTYPE html>
<html>
<head lang="ja">
  <meta charset="UTF-8">
</head>
<body>
<input type="text" id="search" />
<ul id="list">
</ul>
<script src="js/bundle.js"></script>
</body>
</html>
```

### JavaScript

ES6 構文で書いて、[Babel](https://babeljs.io/) で ES5 に変換しています（どのブラウザでも ES6 が動く状況には現状まだないので）。

> Babel の ES6 構文についてはこちらを参考に。
> https://babeljs.io/docs/learn-es2015/


```javascript:app.es6
import Rx from 'rx'
import $ from 'jquery'

// 入力イベントを起点とする
var inputStream = Rx.Observable.fromEvent($('#search'), 'input')
  .filter( // 文字数が０以上の場合のみ stream に載せる
    (e) => e.target.value.length > 0
  )
  .map( // 入力文字を取得
    (e) => e.target.value
  );

// 入力イベントの stream から URLを生成する
var urlStream = inputStream
  .map(
　  // Qiitaの検索API
    (value) => 'http://qiita.com/api/v1/search?q=' + value + '&per_page=10'
  );

// URL の stream から API を発行する
var apiStream = urlStream
  .flatMap(
    (url) => {
      let promise = $.ajax(url);
      return Rx.Observable.fromPromise(promise);
    }
  );

// stream の購読
apiStream.subscribe(
  (res) => {
    // 一旦、リストをクリアする
    $('li').remove();
    // 結果(複数件)に対して１件づつ処理
    res.forEach(
      (element, index, array) => {
        // リストに追加
        $(`<li><a href="${element.url}" target="_blank">${element.title}</a></li>`).appendTo('#result');
      }
    );
  }
);
```

stream に対するデータ処理、という風に宣言的に処理が書け、データバインディングも簡単に実現できました。

## その他

stream の操作でよく使いそうな JavaScript のメソッドをメモしておきます。

参考：[JavaScriptリファレンス | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array)

### forEach

```javascript
[1, 2, 3].forEach(function(currentValue, index, array) {
  console.log(index + ": " + currentValue);
});

// =>
// 0: 1
// 1: 2
// 2: 3
```

### filter

```javascript
var arr = [1, 2, 3, 4, 5].filter(function(currentValue, index, array) {
  return (currentValue >= 3);
});

console.log(arr); // => [ 3, 4, 5]
```

### map

```javascript
var arr = [1, 2, 3, 4, 5].map(function(currentValue, index, array) {
   return currentValue * 2;
});

console.log(arr); // => [ 2, 4, 6, 8, 10 ]
```

### reduce

```javascript
var sum = [1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array) {
  return previousValue + currentValue;
});

console.log(sum); // => 10
```