ES6 の Promise で非同期通信を試す(追記：RxJS でもやってみた)


## 環境

[Babel](https://babeljs.io/) で ES6 のコードを ES5 に変換（Promise Babel の polyfill を利用）し、[Browserify](http://browserify.org/)で依存するモジュール含めてビルドしています。

このオレオレ ES 6環境は GitHub に置いてあります。
→ https://github.com/hkusu/es6-learning-environment

> 主要なライブラリのバージョンは次のとおり。
> ・ babelify：`6.1.2`
> ・ browserify：`10.2.4`

## ダミーの WEB-API を用意

非同期通信といえば WEB-API だと思いますが、今回は JavaScript の `setTimeout()` を利用して、WEB-API を模した関数を用意しました。

> コードは ES6 で書いてます。

```javascript:app.es6
// Babel の現バージョンでは Polyfill が必要なので読み込み
require("babelify/polyfill");

// ダミーの HTTP リクエスト A
var dummyHttpRequestA = () => {
  return new Promise((resolve, reject) => {
    // 2 秒後にメッセージを返す
    setTimeout(() => {
      resolve("success A");
      //reject(new Error("error A")); // エラー発生を模す場合
    }, 2000);
  });
};

// ダミーの HTTP リクエスト B
var dummyHttpRequestB = () => {
  return new Promise((resolve, reject) => {
    // 3 秒後にメッセージを返す
    setTimeout(() => {
      resolve("success B");
      //reject(new Error("error B")); // エラー発生を模す場合
    }, 3000);
  });
};
```

上記では `new Promise()` で Promise オブジェクトを作成していますが、新しめの JQuery だと恐らく `$.ajax()` は Promise を返すはずなので、そのまま Ajax で WEB-API を呼び出せばよいと思います。

## 並列で WEB-API(ダミー) を呼び出す

WEB-API の実行順序を問わない場合です。Promise のスタティックメソッドである `Promise.all()` を利用します。

```javascript:app.es6
// 並列で A、B を API コール（それぞれの結果は配列にマージされる）
function doParallelRequest() {
  return Promise.all([dummyHttpRequestA(), dummyHttpRequestB()]);
}

doParallelRequest().then((message) => {
  // リクエスト A、B が完了した場合
  console.log(message); // => ３秒後に [ 'return message A', 'return message B' ] が出力される
}).catch(e => {
  // リクエスト A、B のどちらかで reject が返された場合
  console.log(e);
});
```

## 直列で WEB-API(ダミー) を呼び出す

WEB-API A の結果を待ってから、WEB-API B を呼び出すパターンです。例えば後続の WEB-API を呼び出す際のパラメータを組み立てるのに、前の WEB-API のレスポンス結果が必要な場合など。

```javascript:app.es6
// 直列で A → B の順番で API コール
function doSerialRequest() {
  return dummyHttpRequestA().then((message) => {
    // リクエスト A が完了した場合
    console.log(message); // => ２秒後に return message A が出力される
    return dummyHttpRequestB();
  });
}

doSerialRequest().then((message) => {
  // リクエスト B が完了した場合
  console.log(message); // => 更に３秒後に return message B が出力される
}).catch(e => {
  // リクエスト A、B のどちらかで reject が返された場合
  console.log(e);
});
```

## 補足

`then` の中で Promise を return した場合、その `then` の実行は中の Promise が resolve されるまで待機します。

この仕組みを使わない場合は、例えば直列の場合だと次のようなコードになります。

```javascript:app.es6
dummyHttpRequestA().then((message) => {
  // リクエスト A が完了した場合
  console.log(message); // => ２秒後に return message A が出力される
  dummyHttpRequestB().then(() => {
    // リクエスト B が完了した場合
    console.log(message); // => 更に３秒後に return message B が出力される
  }).catch(e => {
    // リクエスト B で reject が返された場合
    console.log(e);
  })
}).catch(e => {
  // リクエスト A で reject が返された場合
  console.log(e);
});
```

## 【2015.06.29追記】RxJS を利用した場合

### RxJS の導入

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

### 動作確認

```javascript:app.es6
require("babelify/polyfill");
import Rx from 'rx'

// ダミーの HTTP リクエスト A
var dummyHttpRequestA = () => {
  return new Promise((resolve, reject) => {
    // 2 秒後にメッセージを返す
    setTimeout(() => {
      resolve("success A");
      //reject(new Error("error A")); // エラー発生を模す場合
    }, 2000);
  });
};

Rx.Observable.fromPromise(dummyHttpRequestA())
  .subscribe((message) => {
    console.log(message); // => ２秒後に return message A が出力される
  });
```

### 直列で WEB-API(ダミー) を呼び出す

```javascript:app.es6
require("babelify/polyfill");
import Rx from 'rx'

// ダミーの HTTP リクエスト A
var dummyHttpRequestA = () => {
  return new Promise((resolve, reject) => {
    // 2 秒後にメッセージを返す
    setTimeout(() => {
      resolve("success A");
      //reject(new Error("error A")); // エラー発生を模す場合
    }, 2000);
  });
};

// ダミーの HTTP リクエスト B
var dummyHttpRequestB = () => {
  return new Promise((resolve, reject) => {
    // 3 秒後にメッセージを返す
    setTimeout(() => {
      resolve("success B");
      //reject(new Error("error B")); // エラー発生を模す場合
    }, 3000);
  });
};

Rx.Observable.fromPromise(dummyHttpRequestA())
  .flatMap((message) => {
    console.log(message); // => ２秒後に return message A が出力される
    return Rx.Observable.fromPromise(dummyHttpRequestB());
  })
  .subscribe((message) => {
    console.log(message); // => ３秒後に return message B が出力される
  });
```

`Rx.Observable.fromPromise()` と `flatMap()` を利用すると、Promise な非同期通信をストリームに載せて処理することができます。別の Observable なストリームを起点にする場合は次の通り。

```javascript:app.es6
require("babelify/polyfill");
import Rx from 'rx'

// ダミーの HTTP リクエスト A
var dummyHttpRequestA = () => {
  return new Promise((resolve, reject) => {
    // 2 秒後にメッセージを返す
    setTimeout(() => {
      resolve("success A");
      //reject(new Error("error A")); // エラー発生を模す場合
    }, 2000);
  });
};

// ダミーの HTTP リクエスト B
var dummyHttpRequestB = () => {
  return new Promise((resolve, reject) => {
    // 3 秒後にメッセージを返す
    setTimeout(() => {
      resolve("success B");
      //reject(new Error("error B")); // エラー発生を模す場合
    }, 3000);
  });
};

Rx.Observable.return("foo")
  .flatMap(() => {
    return Rx.Observable.fromPromise(dummyHttpRequestA());
  })
  .flatMap((message) => {
    console.log(message); // => ２秒後に return message A が出力される
    return Rx.Observable.fromPromise(dummyHttpRequestB());
  })
  .subscribe((message) => {
    console.log(message); // => ３秒後に return message A が出力される
  });
```