LoginSignup
14
14

More than 5 years have passed since last update.

コールバック地獄とasync.js

Last updated at Posted at 2014-07-03

サーバ側でnode.js、クライアント側ではjQueryはじめknockout.jsとか使ってますが、どうしてもDB周り(サーバはもちろん、クライアント側もIndexedDBなんか使ってるときとか)は

  • Connectして
  • クエリ投げてカーソル受け取って
  • そのカーソルからデータをフェッチして何かしらのコレクションに突っ込んで
  • 全部突っ込んだらコレクションを返す

みたいな処理を書くのはよくあること
似非コードで書くと

foo.connect(function(err, connection){
    connection.open('bar', 'user', 'password', function(err, db){
        db.query('something query', function(err, cursor){
            var result = [];
            cursor.forEach(function(err, row){
                result.push(row);   
            });
            return result;
        });
    });
});

エラー処理全く書いてないのにコレとか、どんだけインデント深くなるねん・・・と今後加わるであろう処理を考えて戦慄したり、結果が空で?となったり。
結果が空は、今でもちょっとしたサンプル的なものをPHPでPDO使ってるときの感覚で書いてると、今でもたまにやらかします

ということで、少し前からasync.jsを愛用しております
ESの新しいバージョンだとPromiseとかも使えるらしいので今後どうなるかはわかりませんが(´・ω・`)

さて、async.jsですがnode.jsで使う場合はnpmで入れればいいし、ブラウザサイドで使う場合もasync.jsを scriptタグで読み込めばいいだけというお手軽さ。

ドキュメント読むと、いっぱいあって何を使えばいいのやら・・・というのが最初の感想でしたが、今のところよく使っているのは

  • async.waterfall()
  • async.each()
  • async.eachSeries()
  • async.map()
  • async.mapSeries()

ぐらいです。async.parallell()とかも使い方によっては面白いです。
each()とeachSeries()、map()とmapSeries()の違いは順番を保持したまま実行するのか、順不同かもしれないけど全部やることはやるよっていうスタンスの違い

さっきの処理をasync.waterfall()で書きなおすと

async.waterfall([
    function(nextProcess){
        foo.connect(nextProcess);   
    },
    function(connection, nextProcess){
        connection.open('bar', 'user', 'password', nextProcess);
    },
    function(db, nextProcess){
        db.query('something query', nextProcess);
    },
    function(cursor, nextProcess){
        cursor.toArray(nextProcess);
    }
], function(err, result){
    //  途中でエラーがあってもなくても最後はここにたどり着く
    return err ? [] : result;
});

となります

waterfallの名の通り、前の関数でやった処理結果を受けて次の処理→その結果を受けて・・・というパイプライン的な処理をするならこれが一番しっくりきました
MongoDBのNativeドライバのように、コールバックがfunction(err, something)で、エラーがなかった場合はerrにnullが入ってくるようなものあれば、そのままnextProcessというコールバック関数に丸投げできます

コールバック関数はそれ以外のときでも

  • nextProcess(null, 次の, 関数へ, 渡す, 引数, リスト);
  • nextProcess(非null値);

のように呼びます

nextProcess()は第1引数がnullの場合、第2引数以降と最後にコールバック関数をくっつけたものを配列で定義された次の関数に渡してくれます
もしnextProcess()の第1引数に非nullが入ると、配列で定義された関数のパイプラインを抜け出し、配列の外で定義されているfunction(err, result)に飛びます
配列に定義されたパイプラインの最後の関数のnextProcess()もそこに飛んできます

これで、配列の外に定義してある関数function(err, result)はerrがnullならパイプラインに定義されてる処理がつつがなく完了した、非nullなら途中で何かしらエラーがあった、ということがわかるという寸法

14
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
14