Redis の SCAN 系コマンドは RxJS でちょろくなる

- Redis *[SCAN](http://redis.io/commands/scan) コマンドを非同期実行して簡単にアイテムをとってくる方法を説明します
- ES5, ES6, ES7 それぞれの書き方サンプルあります

Redis では [KEYS](http://redis.io/commands/keys) コマンドでキーの一覧を取得することができますが、一度に大量のデータを処理するため Redis の処理時間を占有してしまいます。本番環境での実用性がないため、代わりに少しずつ取得できる [SCAN](http://redis.io/commands/scan)  系コマンドが実装されました。
ただし SCAN 系コマンドはカーソルを使って何度も実行する必要があり、非同期処理が基本の Node だと `redis.scan` や `redis.hscan` をそのまま使うのはなかなか辛いものがあります。
この記事では [RxJS] (https://github.com/Reactive-Extensions/RxJS) を使ってサクッと目的を達成できる方法を紹介します。

# 準備

## まず [redis-scanrx](https://github.com/sukobuto/redis-scanrx) というライブラリを用意します

先日私が作っておいた物がございますので、npm でインストールしておきます。

```
npm install redis-scanrx
```

## 続いて [node_redis](https://github.com/NodeRedis/node_redis) にからめていきます

```js
var redis = require("redis");
require("redis-scanrx")(redis);
```

## あとはいつも通り createClient して準備完了です

```js
var client = redis.createClient();
```

# 盛り付け例

Redis 上から共通プレフィックス `sample-app:` で始まるキーを全て取得し、共通プレフィックスを取り除きつつ 5 つまでを配列にまとめて出力するプログラムを、ES5～ES7 それぞれの書き方で。

## ES5 way

```js
var redis = require("redis");
require("redis-scanrx")(redis);
var client = redis.createClient();

client.scanrx('sample-app:*')
	.map(function (x) {
		// ex: 共通プレフィックスを取り除く
		return x.replace('sample-app:', '')
	})
	.take(5)   // ex: 先頭5つだけ取得する
	.toArray() // ex: 結果を一つの配列にまとめる
	.subscribe(
		function (x) { console.log(x) }
		function (e) { console.error(e) }
		function () { console.log("Completed!") }
	);
```

## ES6 way (Promise)

```js
import redis from "redis";
import scanrx from "redis-scanrx";
let client = scanrx(redis).createClient();

client.scanrx('sample-app:*')
	// ex: 共通プレフィックスを取り除く
	.map(x => x.replace('sample-app:', ''))
	.take(5)   // ex: 先頭5つだけ取得する
	.toArray() // ex: 結果を一つの配列にまとめる
	.toPromise()
	.then(x => console.log(x), e => console.error(e));
```

## ES7 way (async/await)

```js
import redis from "redis";
import scanrx from "redis-scanrx";
let client = scanrx(redis).createClient();

async function main() {
	let result = await client.scanrx('sample-app:*')
		// ex: 共通プレフィックスを取り除く
		.map(x => x.replace('sample-app:', ''))
		.take(5)   // ex: 先頭5つだけ取得する
		.toArray() // ex: 結果を一つの配列にまとめる
		.toPromise();
	console.log(result);
}
main();
```

# 各コマンド

## SCAN ... client.scanrx([pattern])

Redis 上のキーをすべて取得します。
オプションとしてマッチング条件のパターンを指定できます。
[where](http://xgrommx.github.io/rx-book/content/observable/observable_instance_methods/where.html) であとからフィルタリングすることもできますが、パターンによるフィルタであれば Redis との通信量を削減できます。

```js
client.scanrx("key:*")
	.subscribeOnNext(x => console.log(x));
// "key:" で始まるキーのみを取得
```

## SSCAN ... client.sscanrx(key, [pattern])

key に該当する SET に含まれる要素をすべて取得します。
こちらもオプションとして、要素に対するパターンを指定できます。

## HSCAN ... client.hscanrx(key, [pattern])

key に該当する HASH に含まれる field\value をすべて取得します。
こちらもオプションとして、field に対するパターンを指定できます。
取得される各要素は `{ field: "field_1", value: "value_1" }` のようなオブジェクトになります。

```js
client.hscanrx("hash:1")
	.subscribeOnNext(x => console.log(x) );
// => { field: "field_1", value: "value_1" }
// => { field: "field_2", value: "value_2" }
// => { field: "field_3", value: "value_3" }
// => { field: "field_4", value: "value_4" }
// ...
```

## ZSCAN ... client.zscanrx(key, [pattern])

key に該当する Sorted SET に含まれる要素をすべて取得します。
こちらもオプションとして、element に対するパターンを指定できます。
取得される各要素は `{ element: "element_1", score: "1" }` のようなオブジェクトになります。

```js
client.zscanrx("zset:1")
	.subscribeOnNext(x => console.log(x) );
// => { element: "element_1", score: "1" }
// => { element: "element_2", score: "2" }
// => { element: "element_3", score: "3" }
// => { element: "element_4", score: "4" }
// ...
```


> ※RxJS の使い方については LIG INC さんの [「RxJS」初心者入門 – JavaScriptの非同期処理の常識を変えるライブラリ](http://liginc.co.jp/web/js/151272) がわかりやすいです:kissing_closed_eyes: