今更だけどPromise入門

今年のはじめの方からPromiseの話題は耳にしていたけど
結局よくわかってなかったのでここでPromiseのAPIを理解しておこうと思います。

# Promiseとは
非同期の処理をいい感じに使えるAPIパターンです。

Promiseを使ってない場合だと非同期のメソッドを繋げる場合
いわゆるコールバック地獄となってしまいます。

```js
//Promiseを使わない非同期を繋げる場合
A(function(a){
  B(a, function(b){
    C(b, function(c){
      done(c); // ABC
    });
  });
});
```

でもPromiseを使えばメソッドチェーンにすることができコールバック地獄を回避することができます。

```js
A().then(B).then(C).then(done);  // ABC
```

# 使ってみる

## 環境

Promiseが動く環境は
ブラウザの対応状況は[Can I use](http://caniuse.com/promises)で確認でき
Node.jsはv0.11.13から使えます。

## new Promise(callback)

Promiseを利用するにはPromiseをnewでインスタンスを作ります。

作ったインスタンスはそのままリターンさせて
実際の処理はCallbackを渡して書いていきます。

そして成功すればresolveを失敗すればrejectを呼び出します。

試しにNode.jsのfs.readFileをラップしたreadFileAsyncを作ってみます。

```js

fs = require('fs');

function readFileAsync(file) {
  return new Promise(function(resolve, reject){
    fs.readFile(file, function(err, data){
      if (err) {
        reject(err); // errがあればrejectを呼び出す
        return;
      }

      resolve(data); // errがなければ成功とみなしresolveを呼び出す
    });
  });
}

```

## Promise.prototype.then(onFulfilled, onRejected)

実装した関数を使う場合は呼び出した関数の戻り値であるPromiseのメソッド`then`を呼び出すことで値を受け取れます。

第一引数に成功時呼びたい関数を第二引数に失敗時に呼び出したい関数を入れれます。

```js

// 成功時呼ばれる関数
function onFulfilled(data) {
  console.log(data);
}

function onRejected(err) {
  console.log(err);
}

readFileAsync(module.filename)
  .then(onFulfilled, onRejected);
```

## Promise.resolve(val)

もしthenをさらに繋げたい場合(成功時に複数の処理をさせたい時など)は成功時の関数の戻り値に何かしらの値を返してあげることで繋ぐことができます。

```js

function onFulfilled(data) {
  console.log(data);
  return Promise.resolve(data);
}

readFileAsync(module.filename)
  .then(onFulfilled, onRejected)
  .then(function(data){ console.log(data.toString('utf8')); });

```

`Promise.resolve`はnew Promiseを簡略化するためのもので

以下のコードと同等です。

```js
new Promise(function(resolve){
    resolve(val);
});
```

同じように`Promise.reject`もありこちらはテストコードを書く時に一貫性を保つために使われたりします。(来年はテストコードもバリバリ書きたい)

ちなみにさっきの`then`を繋ぐところの何かしらの値というのはPromiseインスタンスじゃなくともPromiseにキャストされるので以下のコードでも同じことになります。

```js
function onFulfilled(data) {
  console.log(data);
  return data;
}
```

## Promise.prototype.catch(onRejected)

Promiseの処理が`reject`された場合`then`の第二引数か`catch`で登録した関数が呼び出されます。

`then`の第二引数に登録する時と後ろに`catch`を繋ぐ場合の違いとしては

`then`が実行中に例外が発生した場合でも`catch`でエラーを取得することができます。

というのも先ほど説明したthen内で返された値やthrowした値がpromiseにキャストしてくれるおかげです。

```js
readFileAsync(module.filename)
  .then(onFulfilled, onRejected); //　onFulfilledでthrowされてもonRejectedが実行されない

readFileAsync(module.filename)
  .then(onFulfilled)
  .catch(onRejected); //実行される

readFileAsync(module.filename)
  .then(onFulfilled) // ここでエラー
  .then(undefined, onRejected); // ここは実行される


```

## Promise.all([p1, p2, ...])

`Promise.all`は配列で渡されたPromiseが全て終わった後に実行したい場合に使います。

渡す配列の中にPromiseじゃないのが混ざっててもPromise.resolveでラップされるので問題なく取得できます。

```js

var files = ['./a.txt', './b.txt', './c.txt'];

Promise.all(files.map(function(file){
    return readFileAsync(file);
  }))
  .then(function(results){ // 結果は配列にまとまって帰ってくる ['a', 'b', 'c']
    return results.map(function(result){
       console.log(result);
       return result;
    });
  })
  .then(...)
  .catch(onRejected); // どれか一つでも失敗すれば呼ばれる
```

## Promise.race([p1, p2, ...])

`Promise.race`は渡したPromiseで最初に解決されたものが呼ばれます。

Endpointが複数あった場合リクエストを同時に投げて、先に帰ってきたやつを使うとかそういう使い道ができるのではないかと思います。

```js
Promise.race(files.map(function(file){
    return readFileAsync(file);
  }))
  .then(function(results){ 
    console.log(results); // 'a'
    return results;
  });
```

# IEとかでPromiseを使う

悲しいことにIEでは全くネイティブでPromiseがサポートされていません
なので[es6-promise](https://github.com/jakearchibald/es6-promise)を利用します。

es6-promiseはPromiseのPolyfillでPromiseがネイティブで利用できない環境で使えるようにしてくれます。

インストール方法はnpmかbowerのどちらでもいけるようです。

ただIE8以下の環境では`.catch`って書くとエラーになるので`['catch']`という風に囲まないといけないようなので注意が必要です。

```js
promise['catch'](function(err){...});
```

# PromiseとCallbackの両方対応させてみる

Callback関数をつけるかつけないかで切り替えてみます。
需要はわからないけど...

```js

function readFileAsync(file, callback) {
  var promise = new Promise(function(resolve, reject){
    fs.readFile(file, function(err, data){
      if (err) {
        reject(err);
        return;
      }

      resolve(data);
    });
  });

  if(!(typeof callback === 'function')){ return promise; }

  promise
  .then(function(data){callback(null, data);})
  .catch(callback);

}

```

# まとめ

Promiseを使うことでCallback地獄を回避しメンテナンス性をあげつつ効率的に処理を進めることができるのでドンドン活用していきたいですね！



あと、本当はPolymerとかMaterialDesignとか書く予定だったんですが

良いサンプルが作れなかったのと

もう少し時間を掛けて作りたいものができたので

その辺を合わせて書く予定です。(できれば今年中に)

# 参考
- [Promise - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)
- [JavaScript Promises: There and back again - HTML5 Rocks](http://www.html5rocks.com/ja/tutorials/es6/promises/)
- [JavaScript Promiseの本](http://azu.github.io/promises-book/)
- [Promiseが実装された - JS.next](http://js-next.hatenablog.com/entry/2013/11/28/093230)