JavaScriptのPromise

JavaScriptのPromiseについて。

## 同期処理 / 非同期処理
### 同期処理
JSはシングルスレッドで、ひとつ処理が終了したら次の処理を実行、という形で、通常は同期処理がおこなわれる。

```js:
var i = 0;

function doSomething1() {
  console.log(i);
}

function doSomething2() {
  i += 1;
  console.log(i);
}

doSomething1();  // 0
doSomething2();  // 1
```

同期処理では、1つの処理が詰まると次の処理が行われない。
(例: APIのレスポンス待機, 複雑な計算 etc.)

そのため、非同期処理ももちろん用意されている。
	
### 非同期処理
1つの処理の終了を持たないまま別の処理に進める方法。
例えば、`setTimeout`, `addEventListener`, jQueryの`$.ajax`などが挙げられる。

```js:
function doSomething1() {
  console.log("hoge");
}

function getUrl(url) {
  $.ajax({
    url: url
  }).done(function() {
    console.log("success");
  }).fail(function() {
    console.error("error");
  });
}

getUrl("https://example.com");  // 非同期
doSomething1();  // 同期
```

## 従来の非同期処理の課題
例えば、API1にリクエストしてアイテムの一覧を取得した後、API2で先頭のアイテムの詳細情報を取得、という処理を書くとする。関数にコールバックを渡す形式にする。

```js:
function getFirstItem(callback) {
  getUrl("/items", function(err, items) {
    if (err) {
      return callback(err);
    }

    getUrl("/items/" + items[0].id, function(err, detail) {
      if (err) {
        return callback(err);
      }
      callback(null, detail);
    })
  });
}

getFirstItem(function(err, item) {
  if (err) {
    console.error(e);
    return
  }
  successProcess(item);
});
```

また、1番目のアイテムを取得した後に2番目も取得しようとすると、例えば以下のようになる。

```js:
function getSecondItem(callback) {
  getUrl("/items", function(err, items) {
    if (err) {
      return callback(err);
    }

    getUrl("/items/" + items[0].id, function(err, detail1) {
      if (err) {
        return callback(err);
      }
      callback(null, detail1);
      
      getUrl("/items/" + items[1].id, function(err, detail2) {
        if (err) {
          return callback(err); 
        }
        
        callback(null, detail2);
      }
    })
  });
}
```

さらに、

- 他の全てのアイテムの詳細を逐次取得していく直列処理
- 各アイテムの取得のリクエストを同時に行い、全てレスポンスが返ってきてから描画、といった並列処理

などを行うとなったらどうするか？なかなか辛そう。

このように、従来の非同期処理には、

- 処理が多段階になると、コールバックのネストがどんどん深くなり、書きづらい / 読みづらい
- コールバックの設定の仕方も様々
	- 非同期処理の関数にコールバック関数を引数として渡す
	- 非同期処理を行うオブジェクトにコールバック関数を登録
	- etc.

といった課題があった。そこで登場したのがPromise。

## Promise
- 非同期処理を統一的に扱う方法を定義した、 **オブジェクト**
	- 非同期処理を行う関数はPromiseを戻り値として返し、呼び出し側はPromiseにコールバック関数を登録
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
- ES2015で導入、[ブラウザ対応状況](http://caniuse.com/#feat=promises)

Promiseを使うと、上記の処理は以下の様な感じになって短く書けるようになる。

```js:
function getFirstItem() {
  return getUrl("/items").then((items) => {
    return getUrl("/items/" + items[0].id);
  });
}

getFirstItem().then((item) => {
  successProcess(item);
}).catch((e) => {
  console.error(e);
});
```

## Promiseの使い方
- Promiseオブジェクトを、`Promise`コンストラクタで生成
- コンストラクタの引数に、`resolve`と`reject`という2つの関数を渡す

```js:
const promise = new Promise((resolve, reject) => {
  if ('処理が成功') {
    resolve(1);
  } else {
    reject(new Error('error'));
  }
});
```

こうして生成したPromiseオブジェクトは、`then`とセットで使う。

```js:
promise.then((value) => {
  console.log(value);  // 1
}, (error) => {
  console.error("error:", error.message);
});
```

Promiseの使い方の基本は以下。

- `resolve()`を呼ぶと、「処理が成功した状態 (fullfilled)」になる
	- 引数に値を受け取れ、`then`のコールバックに渡せる
- `reject()`を呼ぶと、「処理が失敗した状態 (rejected)」になる
	- `resolve`と同様に引数に値を渡せる
	- `Error`オブジェクトを渡すのが通例
- Promiseオブジェクトは`then()`メソッドを持ち、処理が成功 or 失敗した際の処理をコールバックとして渡せる
	- 処理が成功だと第1引数の関数を実行
	- 失敗だと第2引数の関数を実行 ※省略可
- `then()`が返すのもPromise

### Promiseの状態
上述したとおり、Promiseには処理に応じた「状態」があり、次の3つのうちの1つになる

- pending: 初期状態、まだ処理が成功も失敗もしていない
- fullfilled: 処理が成功し、完了した状態
- rejected: 処理が失敗した状態

基本的に、Promiseオブジェクトはpendingの状態で生成され、関連付けた処理の実行結果に応じて、fullfilled あるいは rejectedの状態に変化する。この状態の変更は一方通行で、pendingに戻ることはない。

そして、fullfilled もしくは rejected への状態変更が起こった時に実行する関数を登録できるのが`then()`。

## 使ってみる例
指定時間後に何らかの処理を実行する関数`wait`を実装する。

```js:wait.js
function wait(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`wait: ${time}`);
    }, time);
  });
}

wait(3000).then((value) => {
  console.log(value);
  return `finish ${value}`;
}).then((value) => {
  console.log(value);
}).then((value) => {
  console.log(value);
});
```

`setTimeout`という非同期アクションの実行結果に応じた処理が行われる（上記は処理成功が前提になっているが）。`wait`は未来のある時点でPromiseを返す。Promiseを利用することで、同期処理と同じように値を返せるようになった。

コードを実行すると、約3,000ms後に"wait: 3000"が出力され、そのあとすぐに"finish wait: 3000"と"undefined"が出力される。

```
$ node -v
v6.3.1

$ node wait.js
wait: 3000
finish wait: 3000
undefined
```

### `then`のメソッドチェーン
上記の例では、3つの`then()`を繋げている。先述したように、`then()`が返すのもPromiseオブジェクトであるためこのようなことが可能、何個でもチェーンできる。

- `then`の中で`return <値>`することは、`resolve(<値>)`と等しい
- 何も`return`しないときは、`resolve()`と同じ

このようなルールがある。

### `catch`
アイテムの一覧 => 詳細を取得する非同期処理をもう一度。

```js:
function getUrl(url) {
  return new Promise((resolve, reject) => {
    let xhr = XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      } else {
        reject(new Error(xhr.statusText));
      }
    };
    xhr.onerror = () => {
      reject(new Error(xhr.statusText));
    };
    xhr.send();
  });
}

function getFirstItem() {
  return getUrl("/items").then((items) => {
    return getUrl("/items/" + items[0].id);
  });
}

getFirstItem().then((item) => {
  successProcess(item);
}).catch((e) => {
  console.error(e);
});
```

`getUrl()`でPromiseを返し、通信の成功/失敗で処理を分岐させる。
`getFirstItem()`で、2つのAPIエンドポイントに順次リクエストし、最後まで処理が完了したら`successProcess()`、失敗したらエラーを出力。

ここで`catch((e) => {})`を利用しているが、これは`then(null, (e) => {});`と同義。
`then()`のチェーンの最後に付けておけば、一連の処理中に`reject()`で明示的に処理失敗を宣言した場合だけでなく、予想外のエラーも拾ってくれる。

これまで書いてきたが、Promiseが統一的なインターフェースを提供してくれるおかげで、コードの見通しがだいぶ良くなる。ただし、これだけで終わりではなく、非同期処理の並列実行/直列実行も簡単に実装できるようになる。

## 並列処理
APIを多数コールし、全てのレスポンスが返った後に処理を行う、という場合は`Promise.all()`を使うと楽に書ける。

```js:parallel.js
function task(value, ms) {
  console.log(`${value} called`);

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(value);
      console.log(`${value} fullfilled`);
    }, ms);
  });
}
function taskA() { return task("A", 3000); }
function taskB() { return task("B", 2000); }
function taskC() { return task("C", 1000); }

Promise.all([taskA(), taskB(), taskC()]).then((values) => {
  console.log(values);
  console.info('all fullfilled');
}).catch((error) => {
  console.error(error);
});
```

コードを実行すると、以下の出力に。

```
$ node parallel.js
A called
B called
C called
C fullfilled
B fullfilled
A fullfilled
[ 'A', 'B', 'C' ]
all fullfilled
```

taskA, B, Cの処理は配列のインデックス順にほぼ同時に開始され、`task()`の最初の`console.log`が実行される。その後処理は`setTimeout`に到達し、コールバック実行までの時間が最も短い`taskC`から順に`resolve()`され、それと同時にログに出力が行われる。つまり、コードの実行から約1,000ms後にC、2,000ms後にB, 3,000ms後にAのPromiseがfullfilledになる。

`Promise.all()`は

- `all()`の引数の配列内のPromiseの状態が全て fullfilled になったら、`all()`自体も fullfilled
- 1つでも rejected になると、`all()`も rejected

というルールになっており、上記の例ではいずれの処理も成功しているため、`all()`が返すPromiseも fullfilled になる。結果、`taskA`が終了した約3,000ms後に、`catch()`ではなく`then()`の引数のコールバック関数が実行される。コールバック関数の引数(`values`)には、並列処理したそれぞれのPromiseが`resolve()`で返した値が配列として入っているのが分かる。

`all()`は全てのPromiseを一斉に実行し、全てが fullfilled になるのを待ってから処理を行ってくれるため、冒頭に書いたような「APIを多数コールし、全てのレスポンスが返った後に処理を行う」ケースなどで非常に有用である。

※1つでも rejected になれば、`all()`もすぐ rejected となり、`catch()`側の処理に入る

## 直列処理
非同期処理が成功したら次の非同期処理...といった直列 (逐次) 実行はどうするか。

#### `then()`をチェーンさせる
```js
taskA()
  .then(taskB)
  .then(taskC)
  .catch(...);
```

数が増えると少しめんどいし、見難くなってくる。

#### `Array.prototype.reduce()`を使う
調べるとよく出てくるやり方。

```js
const tasks = [taskA, taskB, taskC].reduce((promise, task) => (
  promise.then(task)
), Promise.resolve());

tasks.catch(...);
```

`reduce()`を使ってチェーンを繋げていく方式。
チェーンの先頭になる`Promise.resolve()`はStaticメソッドで、コンストラクタを利用せずにPromiseオブジェクトを生成でき、その状態は初めから fullfilled である。（同様に`Promise.reject()`も存在する)

これでも良さげだが、さらに良いやり方があるらしい。Generatorというのを利用する。

## Generatorを使った直列処理
### Generatorとは
- こちらもES2015で導入された概念
- 関数の任意の場所で処理を中断/再開できる仕組みを提供
- 非同期処理を同期処理のように書けるようになる

ここはまだちょっと理解が怪しい

### Generatorの利用例
Generatorを使った簡単なコードを書いてみる。
ジェネレータ関数は、`function`と関数名の間に`*`を付けて定義する。また、ここで欠かせないのが`yield`。


```js:generator.js
function *generator() {
  yield 1;
  yield 2;
  return 3;
}

const g = generator();
console.log(g);
console.log(g.next());
console.log(g.next());
console.log(g.next());
```

コードの実行結果は以下

```
$ node generator.js
{}
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: true }
```

ジェネレータ関数は実行するとイテレータを返し、`next`メソッドというのを持っている。これを実行すると、`generator()`内の最初の`yield`まで実行され、`yield`式の結果を`value`として返す。また`next`を実行すると次の`yield`まで実行される。`return`あるいは実行する`yield`が無くなると、イテレータの`done`が`true`になる。

簡単な例で示したが、このようにGeneratorを利用することで、関数内の処理の中断・再開を行える。

### 非同期処理の直列処理
Generatorを利用したPromiseの直列処理も簡単に書ける。ここでは`co`というライブラリを利用する。

```js
const co = require('co');

co(function *() {
  yield taskA();
  yield taskB();
  yield taskC();
});
```

同期処理のようにしか見えないが、それぞれの`task`がPromiseを返すまで処理中断 => 返したら再開、という挙動になり、結果として非同期処理が逐次処理される。これはすごい。。。

次の世代のECMAScriptでは、`async`/`await`という機能で、このGeneratorが標準搭載されるそう。

## まとめ
- 非同期処理が複雑になればなるほど、Promiseが威力を発揮する
- 統一的な記法になるので、慣れてしまえば書きやすい・読みやすい
- Promiseはただのクラスなので、ポリフィルライブラリを読み込むだけでも利用可能
    - 例えば、[stefanpenner/es6-promise: A polyfill for ES6-style Promises](https://github.com/stefanpenner/es6-promise)
- Generatorは新しい概念だが、逐次処理などで有用
    - もうちょい学習が必要かな。。

## 参考
- [JavaScript Promiseの本](http://azu.github.io/promises-book/)
- [JavaScriptのPromiseオブジェクトについて調べた事 | QUARTETCOM TECH BLOG](http://tech.quartetcom.co.jp/2016/03/22/javascript-promise/)
- [Promiseで簡単！JavaScript非同期処理入門【前編】 | HTML5Experts.jp](https://html5experts.jp/takazudo/17107/)
- [JavaScript Promises: There and back again - HTML5 Rocks](http://www.html5rocks.com/ja/tutorials/es6/promises/)
- [ES6時代のJavaScript - クックパッド開発者ブログ](http://techlife.cookpad.com/entry/2015/02/02/094607)
- Web+DB Vol.87