node.jsでpromiseを使う機会があったので、自分のメモ用に残します。
Promiseって何?
Promiseって何よ?ってことで、まずはググる。
Promiseとは非同期処理を上手く扱う為のAPIであり、パターンである。
非同期の処理の完了後に続けて処理を行いたいとき、よくコールバックパターンが使われるが、処理が連続するとコールバック地獄と言われる分かりづらいソースコードになってしまう。
また、複数の非同期処理が完了した時に処理を行うなど、コールバックパターンでは難しい事をスマートにできるのがこのPromiseである。
要するに、同期処理したいけど、コールバック地獄で見づらくなるのを防ぐために使うAPIってことかな?
npmのパッケージとしては使用可能なモジュールはいくつかあるが、今回はQを使用する。
Q の基本 (.when .then .done)
簡単なQの記述は下記のようになる
var Q = require('q');
//非同期処理
var stepA = function(val) {
var d = Q.defer();
setTimeout(function(){
console.log(val);
d.resolve('fuga');
}, 500);
return d.promise
};
//同期処理
var stepB = function(val) {
var d = Q.defer();
console.log(val);
d.resolve('piyo');
return d.promise
};
Q.when('hoge')
.then(stepA)
.then(stepB)
.done(function(val){
console.log(val);
});
下記はresult
hoge
fuga
piyo
Q.whenで最初の引数をset, .then(stepA)で、whenでsetした引数でstepAを呼び出し、d.resolveで次のメソッドへの引数をsetする。
.doneは最後に呼ばれる。
このようにPromiseを用いることで、同期・非同期に関係なく簡単に逐次処理を書くことが出来る。
forEachの処理 (.all)
下記の様な、forEachに対して、Promiseを用いる。
var func = function(val){
//非同期処理
setTimeout(function(){
var result = val*val;
d.resolve(result);
}, 500);
return result;
};
var arr = [1, 2, 3, 4, 5];
arr.forEach(function(val, index){
result = func(val);
console.log(result);
});
この場合、下記のように書く
var func = function(val){
var d = Q.defer();
//非同期処理
setTimeout(function(){
var result = val*val;
d.resolve(result);
}, 500);
return d.promise;
};
var arr = [1, 2, 3, 4, 5];
Q.all(arr.map(func));
.then(function(data){
console.log(data);
});
[ 1, 4, 9, 16, 25 ]
.allは、複数のメソッドを同時に実行して、全て完了したら、次の処理を行う。
.allと合わせてforEachのmapやreduceメソッドを使うことで、arrayの全要素に処理を行ってから、次の処理に移れる。
###.spread
.spreadは.allの処理全てが終わった後に実行される。この時、spreadで実行するメソッドの引数には、.allでresolveした値が入る。
.spreadを用いる場合、上記処理は下記のようになる。
var arr = [1, 2, 3, 4, 5];
Q.all(arr.map(func));
.spread(function(res1, res2, res3, res4, res5){
data = [res1, res2, res3, res4, res5];
console.log(data);
});
try-catch (.fail)
Qでは、.failを用いることで、ステップを途中で抜けることが出来る。
var stepA = function(val) {
var d = Q.defer();
setTimeout(function(){
console.log("StepA:"+val);
d.resolve('fuga');
}, 500);
return d.promise
};
var stepB = function(val) {
var d = Q.defer();
setTimeout(function(){
console.log("StepB:"+val);
d.reject('piyo');
}, 500);
return d.promise
};
var stepC = function(val) {
var d = Q.defer();
setTimeout(function(){
console.log("StepC:"+val);
d.resolve('hoge');
}, 500);
return d.promise
};
function QTest(){
Q.when('hoge')
.then(stepA)
.then(stepB)
.then(stepC)
.fail(function(val){
console.log("fail:"+val);
var d = Q.defer();
d.resolve('fail');
return d.promise
})
.done(function(val){
console.log("done:"+val);
});
}
StepA:hoge
StepB:fuga
fail:piyo
done:fail
このように、ステップ内でrejectを検知したら、.failを実行してステップが終了する。
なお、failでステップを途中で抜けても、.doneは最後に必ず実行される。