Promiseについて0から勉強してみた

ES6を使う機会がありそうで、Promiseについて全然知らなかったので、実際に書きながら勉強してみたときのメモ。

なお、以下を参考にさせて頂きました。
0から勉強する時でもとても分かりやすかったです。

[JavaScript Promiseの本](https://azu.github.io/promises-book/#introduction)

# 環境

+ Node.js v4.2.2

# Promiseとは

+ 非同期処理を操作できる
+ 非同期処理の成功時(resolve)、失敗時(reject)の処理を明示的に書くことが出来る
+ 非同期処理を平行・直列に実行させることが出来る

# とりあえずやってみる

とりあえず、参考にさせて頂いたサイトのコードを書いてみました。

```promise-workflow.js
function asyncFunction() {
    
    // Promiseオブジェクトを返却する.処理成功時にはresolveが呼ばれる
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            // 成功
            resolve('Async Hello world');
        }, 16);
    });
}

asyncFunction().then(function (value) {
    // 非同期処理成功
    console.log(value);    // => 'Async Hello world'
}).catch(function (error) {
    // 非同期処理失敗。呼ばれない
    console.log(error);
});
```

上記では非同期で実行される`setTimeout`メソッドをPromiseを使って実行しています。
`resolve`メソッドを呼びだす事で非同期処理の成功を示し、`then`メソッドの引数の処理を実行しています。

以下は`にreject`関数を呼び出すことで非同期処理の失敗を示し、`catch`メソッドの引数の処理を実行しています。

```promise-workflow.js
function asyncFunction() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      // 失敗
      reject(new Error('Error'));
    });
  });
}

asyncFunction().then(function (value) {
  console.log(value);
}).catch(function (error) {
  // 呼ばれる
  console.log(error);  // => 'Error'
});
```

# メソッドチェーンについて理解する

```chain.js
function taskA () {
  console.log("TaskA");
}

function taskB () {
  console.log("TaskB");
}

function onRejected(error) {
  console.log("error = " + error);
}

var promise = Promise.resolve();
promise
  .then(taskA)
  .then(taskB)
  .catch(onRejectted);
```

上記を実行すると`taskA,taskB`と順番に標準出力が表示されます。

+ var promiseのresolve()が実行され
+ then(taskA)が実行され
+ then(taskB)が実行された

という流れだと思います。

なお、上記のように記載した場合、taskAもしくはtaskBのいずれかでエラーが発生した場合に`onRejected`メソッドが呼び出されます。
エラーが発生したとは具体的に以下のいずれかの場合となります。

+ 例外(thrown new Error)が発生した
+ rejectが呼び出された


```chain.js
function taskA () {
  throw new Error('Error');
  console.log("TaskA");
}

function taskB () {
  console.log("TaskB");
}

function onRejectted(error) {
  console.log("error = " + error);
}

var promise = Promise.resolve();
promise
  .then(taskA)
  .then(taskB)
  .catch(onRejectted);
```

上記実行すると`error = Error: Error`のみ標準出力されます。
taskBの関数については呼び出されません。

# 非同期処理を並列で行う

一つの非同期処理をPromiseにしても嬉しくないので、並行で非同期処理を行う方法を確認します。
(taskAが終わってtaskBをやって。。。というような逐次処理は次の章で)

## Promise.all()

`Promise.all()`メソッドはPromiseオブジェクトの配列を受け取り、全てのPromiseオブジェクトがresolveされたタイミングで`then`が呼び出されます。

書いて確認してみましょう。

```promise-all.js
var taskA = new Promise(function(resolve, reject) {
  setTimeout(function () {
    console.log('taskA');
    resolve();
  }, 16);
});

var taskB = new Promise(function(resolve, reject) {
  setTimeout(function () {
    console.log('taskB');
    resolve();
  }, 10);
});

var before = new Date();
Promise.all([taskA, taskB]).then(function () {
  var after = new Date();
  var result = after.getTime() - before.getTime();
  console.log(result);
});
```

上記の結果は以下のようになります。

```bash
taskB
taskA
16
```

上記より以下であることが分かります。

+ taskAとtaskBは並行で実行されている(直列で実行されている場合には差分の秒数として26が表示されるはず)
+ taskAとtaskBが終わってからthenが呼び出されている

ちなみにrejectを返却するとどうなるのかやってみます。

```hoge.js
var taskA = new Promise(function(resolve, reject) {
  setTimeout(function () {
    console.log('taskA');
    resolve();
  }, 16);
});

var taskB = new Promise(function(resolve, reject) {
  setTimeout(function () {
    console.log('taskB');
    reject();
  }, 10);
});

var before = new Date();
Promise.all([taskA, taskB]).then(function () {
  var after = new Date();
  var result = after.getTime() - before.getTime();
  console.log(result);
}).catch(function () {
  console.log('error');
});
```

結果は以下です。

```bash
taskB
error
taskA
```

上記より以下であることが分かります。

+ 配列に指定されたいずれかのPromiseでrejectが呼び出されたタイミングで`catch`メソッドが呼びされ、実行される
+ catchメソッドが呼びされても終了していないPromiseの処理は継続される
+ 全てのPromiseの処理が終わっても`then`メソッドは呼び出されない

Promiseのいずれかでもエラーになった時点で他のPromiseの処理を待たずに終了させたい場合には`process.exit(1)`など使えば良さそうですね。

## Promise.race()

Promise.all()は全てのPromiseが呼び出された後にthenメソッドが呼び出されました。(catchではなく)
Promise.race()は一つでもresolve, rejectが呼び出されたら、`then`もしくは`catch`が呼びされます。

確認します。

```hoge.js
var taskA = new Promise(function(resolve, reject) {
  setTimeout(function () {
    console.log('taskA');
    resolve();
  }, 16);
});

var taskB = new Promise(function(resolve, reject) {
  setTimeout(function () {
    console.log('taskB');
    resolve();
  }, 1);
});

var before = new Date();
Promise.race([taskA, taskB]).then(function () {
  var after = new Date();
  var result = after.getTime() - before.getTime();
  console.log(result);
}).catch(function () {
  console.log('error');
});
```

結果

```bash
taskB
5
taskA
```

1という表示を期待したのですが、msなので多少は仕方なさそうですが、以下の結果が分かりました。

+ いずれかのPromiseのresolveが呼びされたタイミングで`then`メソッドが実行される
+ thenメソッドが呼びされた後もまだ終了していないPromiseの処理は継続

こちらもrejectが呼び出された場合について確認します。


```hoge.js
var taskA = new Promise(function(resolve, reject) {
  setTimeout(function () {
    console.log('taskA');
    resolve();
  }, 16);
});

var taskB = new Promise(function(resolve, reject) {
  setTimeout(function () {
    console.log('taskB');
    reject();
  }, 1);
});

var before = new Date();
Promise.race([taskA, taskB]).then(function () {
  var after = new Date();
  var result = after.getTime() - before.getTime();
  console.log(result);
}).catch(function () {
  console.log('error');
});
```

結果は以下。
Promise.all()と同じ挙動ですね。

```bash
taskB
error
taskA
```

参考ページにも書いてありましたが、Promiseには処理を途中でキャンセルというのはないみたいですね。

# 非同期処理を逐次処理で実行する

Promise.all()及びPromise.race()は並行で非同期処理を制御できるメソッドでした。

次に逐次処理(直列処理)について確認します。

まずは、参考サイトを参考にしてシンプルに書いてみます。

[ECMAScript6のアロー関数とPromiseまとめ - JavaScript](http://qiita.com/takeharu/items/c23998d22903e6d3c1d9)


```hoge.js
Promise.resolve()
.then(function () {
  return new Promise(function(resolve, reject) {
    setTimeout(function () {
      console.log('taskA');
      resolve('taskA death');
    }, 16);
  });
})
.then(function(value) {
  return new Promise(function(resolve, reject) {
    setTimeout(function () {
      console.log(value);
      console.log('taskB');
      resolve('taskB death');
    }, 1);
  });
})
.then(function (value) {
  console.log('then');
  console.log(value);
}).catch(function (error) {
  console.log(error);
});
```

シンプルですね。

下記参照させて頂いたページを参考に並列処理の際に利用した関数を逐次処理させます。こちらは関数に分かれているのでテストもしやすそうです。

[4.8. Promiseによる逐次処理](https://azu.github.io/promises-book/#promise-sequence)

```hoge.js
function sequenceTasks(tasks) {
    function recordValue(results, value) {
        results.push(value);
        return results;
    }
    var pushValue = recordValue.bind(null, []);
    return tasks.reduce(function (promise, task) {
        return promise.then(task).then(pushValue);
    }, Promise.resolve());
}

var promises = {
  doTaskA: function() {
    return taskA().then();
  },
  doTaskB: function() {
    return taskB(value).then();
  }
};

function taskA() {
  return new Promise(function(resolve, reject) {
    setTimeout(function () {
      console.log('taskA');
      resolve('taskA death');
    }, 16);
  });
}

function taskB(value) {
  return new Promise(function(resolve, reject) {
    setTimeout(function () {
      console.log(value);
      console.log('taskB');
      resolve('taskB death');
    }, 1);
  });
}

function main() {
 return sequenceTasks([promises.doTaskA, promises.doTaskB]);
}

main().then(function(value) {
  console.log('then');
  console.log(value);
// taskAもしくはtaskBでエラーの場合に呼び出される
}).catch(function(error) {
  console.log(error);
});
```

`sequenceTasks`メソッドを使うとPromiseでの実行結果がそれぞれpushされるので最終的に後から結果の取得を行うこともできます。


```bash
taskA
[ 'taskA death' ]
taskB
then
[ 'taskA death', 'taskB death' ]
```

以下が確認できました。

+ 逐次(直列)で非同期処理を行える
+ 1つ目の非同期処理の結果を2つ目の非同期処理に渡すことが出来る
+ taskAもしくはtaskBでエラーがあった場合の処理を1か所に書くことが出来る