[node.js]asyncを使って非同期処理を制御 | 2年目エンジニアの戯言

2年目エンジニアの戯言

暇な時に更新。
すごく当たり前なことも多々書いてますが気にせずに。

     

asyncを使って非同期処理を制御

先日同僚と飲みの席でnode.js@0.12系のyieldについて語っていたのですが、いざ調べてみるとまだあまり情報が出ていないですね。調べてか考察でも書こうと思っていたのですが残念です。しょうがないのでasyncについて書いていきます。

javascriptの非同期処理については今回はあまり深く語りません。確かに初めてjavascriptやったとき非同期処理に戸惑いを感じました。しかし慣れれば大したことないですよねw ところでasyncってエーシンクと読むのかアシンクと読むのか。僕はajaxのことをエージャックスと読みますがasyncのことをアシンクと読んでます。同じ英単語からもじられたモジュール名なのでおかしな話ですねww

基礎編 順次処理と並行処理

今回はasync.seriesasync.waterfallasync.parallelの3つを説明したいと思います。普段僕はasync.seriesしか使いませんが。というよりasync.waterfallは個人的に嫌いですw async.seriesasync.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.twonullが入るどころかキーすらできません。

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