1. jiroshin
Changes in body
Source | HTML | Preview
@@ -1,413 +1,413 @@
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();
+ return taskA();
},
- doTaskB: function() {
- return taskB(value).then();
+ doTaskB: function(value) {
+ return taskB(value);
}
};
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か所に書くことが出来る