asyncを使って非同期処理を制御
先日同僚と飲みの席でnode.js@0.12系のyieldについて語っていたのですが、いざ調べてみるとまだあまり情報が出ていないですね。調べてか考察でも書こうと思っていたのですが残念です。しょうがないのでasyncについて書いていきます。
javascriptの非同期処理については今回はあまり深く語りません。確かに初めてjavascriptやったとき非同期処理に戸惑いを感じました。しかし慣れれば大したことないですよねw ところでasyncってエーシンクと読むのかアシンクと読むのか。僕はajaxのことをエージャックスと読みますがasyncのことをアシンクと読んでます。同じ英単語からもじられたモジュール名なのでおかしな話ですねww
基礎編 順次処理と並行処理
今回はasync.series
とasync.waterfall
とasync.parallel
の3つを説明したいと思います。普段僕はasync.series
しか使いませんが。というよりasync.waterfall
は個人的に嫌いですw async.series
とasync.waterfall
は順次処理で、async.parallel
は並行処理です。
async.series(tasks, [callback])
非同期処理を順番に実行したいときに使用します。第一引数にtasksを、第二引数にすべてのtaskが終わった時の処理を与えます。例を用いて説明したいと思います。
async.series([
function(next) {
setTimeout(function() {
console.log('one done!');
next(null, 'one');
}, 200);
},
function(next) {
setTimeout(function() {
console.log('two done!');
next();
}, 100);
},
function(next) {
setTimeout(function() {
console.log('three done!');
next(null, 'three', 3, '3');
}, 150);
}
], function complete(err, results) {
console.log(JSON.stringify(results));
});
// --- output ---
// one done!
// two done!
// three done!
// ["one",null,["three",3,"3"]]
説明のために無名関数にcomplete
と名づけたりsetTimeout
の時間をずらしています。上から順番に実行しているので出力結果は順番通りになりましたね。各taskのnext
関数を呼び出すと次のtaskへと移ります。next
の第一引数にはerrorを渡せるようになっていて、もしerrorを渡したとするとその時点でcomplete
へ移行し、complete
の第一引数にそのerrorが渡ってきます。tasksの途中で予期せぬことが起きたらその方法で抜けることができます。next
の第二引数に値を渡すとresults
に入ってきます。渡さななくてもnull
が入ります。第二引数以降にさらに値を渡すとresults
の中身は配列になります。例のthreeの部分です。
第一引数は配列ではなくてもよく、もしオブジェクトだとcomplete
に渡ってくるresults
のフォーマットが変わります。
async.series({
one: function(next) {
setTimeout(function() {
console.log('one done!');
next(null, 'one');
}, 200);
},
two: function(next) {
setTimeout(function() {
console.log('two done!');
next();
}, 100);
},
three: function(next) {
setTimeout(function() {
console.log('three done!');
next(null, 'three', 3, '3');
}, 150);
}
}, function complete(err, results) {
console.log(JSON.stringify(results));
});
// --- output ---
// one done!
// two done!
// three done!
// {"one":"one","three":["three",3,"3"]}
tasksに使ったキーごとに結果が入っているオブジェクトが返るようになりました。前者のやり方でresults[0]
みたいなアクセスの仕方をすぐらいなら後者のほうが良さそうですね。実はこの記事を書くためにおさらいでasyncのドキュメント見て知りましたww 注意すべきは前者と違いresults.two
にnull
が入るどころかキーすらできません。
async.waterfall(tasks, [callback])
次は個人的に使わないasync.waterfall
の説明ですw これもasync.series
と同じく順次実行をしますがtaskの書き方やresults
の中身が変わります。
async.waterfall([
function(next) {
setTimeout(function() {
console.log('one done!');
next(null, 'one', 1);
}, 200);
},
function(arg1, arg2, next) {
console.log(arg1, arg2);
setTimeout(function() {
console.log('two done!');
next();
}, 100);
},
function(next) {
setTimeout(function() {
console.log('three done!');
next(null, 'three', 3, '3');
}, 150);
}
], function complete(err, arg1, arg2, arg3) {
console.log(arg1, arg2, arg3);
});
// --- output ---
// one done!
// one 1
// two done!
// three done!
// three 3 3
async.series
ではcomplete
に結果が集まる形でしたがasync.waterfall
では次のtaskに値が渡されます。function(arg1, arg2, next) {
のように1つ前のtaskが渡してくる引数によってargumentsの形が変わります。next
の位置がコロコロ変わるので僕はここが嫌いであったりしますw でも確かに順次実行しているのだから次のtaskに値を渡すシーンは多くありそうですね。async.series
と違いtasksは配列でないと動きません。
async.parallel(tasks, [callback])
今回説明する中では唯一の並行処理です。tasksを同時に処理していき、すべて終了したらcallbackに返ってきます。
async.parallel([
function(next) {
setTimeout(function() {
console.log('one done!');
next(null, 'one');
}, 200);
},
function(next) {
setTimeout(function() {
console.log('two done!');
next();
}, 100);
},
function(next) {
setTimeout(function() {
console.log('three done!');
next(null, 'three', 3, '3');
}, 150);
}
], function complete(err, results) {
console.log(JSON.stringify(results));
});
// --- output ---
// two done!
// three done!
// one done!
// ["one",null,["three",3,"3"]]
出力結果を見ての通りasync.series
のときと順番が変わりましたね。setTimeout
で時間をずらしているためasync.parallel
を使って同時に処理をするとこの結果になります。あとはasync.series
と同じ書式です。tasksは配列でもオブジェクトでも可能です。
今回基礎編ということで代表的な3つの関数を紹介しました。ただせめてあとmapやmapSeries等の配列処理の説明もしなきゃですね。
初めてこういう技術的な記事書いてみましたがソース確認しながらだと時間かかりますね。文字数これだけですがほぼ1時間かかりましたw asyncのドキュメントはコードベースで説明されていたので読みやすかったのですが、文章だけのドキュメントだと英語苦手な僕はそれだけで積みます(汗 そういうときはドキュメントを翻訳するのは諦めてコード読みながら理解していきますw