質問: friends[i]['user_id']: object.sendMessage(body, user_id); } callback(null); }, ] ); ``` かなり簡略化していますが、 毎回こんな感じでasyncを使ってやっていかなくてはならないんでしょうか? やりたいことを実現できるライブラリがnode.jsにしかなくて仕方なく使っています。
回答: Node.js・JavaScriptのシングルスレッドでパフォーマンスを上げるために、 コールバック関数を利用したノンブロッキングなフローという事で根本的な解決は不可能です。 コールバック地獄は避けられません。 小細工的なところでAsyncとPromiseの2通りの逃げ方ができますが、どちらも大差ないでしょう。 対策その1:処理を関数化して細分化する そのまんまです。 Node.jsで増えたcommon.jsの機能であるrequireをフル活用すればもう少しきれいになると思います。 対策その2:AltJSに乗り換える どんなに頑張ってもJavaScriptは所詮JavaScriptです。 CoffeeScriptやTypeScriptといったAltJSの名前を聞いたことはありませんか? ほら、あのコンパイルするとJavaSciriptに変換されるメタ言語のことです。 raccyさんの記事が参考になるかと思います。 よくNode.jsで大規模開発する場合、型がないので次第につらくなる、型の強固さを得たTypeScriptに移ろうという話はありますがとんでもない! 大規模開発しない人はすべからくJavaScriptを捨ててCoffeeScript、もしくはそれの進化形であるLiveScriptへ移行すべきです。 ちょっとCoffeeScriptで上記のコードを書いてみますね。 コード量が半分くらいで収まるので刮目してみてください。 あ、因みにfor文はバグの温床なのでArray.forEachに書き換えています。 ``` async = require('async') sqlite3 = require("sqlite3").verbose() db = new sqlite3.Database('db.sqlite3') db.all "SELECT * FROM friend", (err, friends)-> db.all "SELECT * FROM message", (err, messages)-> friends.forEach (friend)-> body = messages.phase[friend.phase] user_id = friend.user_id object.sendMessage body, user_id ``` CoffeeScriptはJavaScriptにRubyやPythonのエッセンスを少し加えた程度の言語なので、 horikさんの腕なら何となくでも読めるはずですし、書けるはずです。 変換後の正確なJavaScriptコードは公式サイトに行けば変換できます。 コールバック地獄の問題の本質は、ネストや波括弧・丸括弧だらけになりロジック自身が見えづらいことです。 CoffeeScriptでfunctionや括弧という決まり事のノイズをそぎ落として、 コアなロジック部分だけ表示することで各段に読みやすくなっているかと思います。 ある程度のレベルならAsyncやPromiseという小技に頼る必要がなくなります。 しかも使い方は超絶簡単、上記ファイルを「send_messages.coffee」として保存した前提で実行してみますね。 ``` $ npm install -g coffee-script $ coffee send_messages.coffee ``` なんと実行する際のnodeコマンドがcoffeeコマンドに変わっただけです。 AWSのLambdaのようなネイティブのNode.jsしか使えない環境でも、こんな感じのJSファイルから実行できます。 ``` require('coffee-script'); require('./send_messages.coffee'); ``` いかがでしたでしょうか? しかし、CoffeeScriptは時代遅れなのでLiveScriptを使いましょうね。 こちらは関数型言語のエッセンスを加えたJavaScript版Scalaとでもいうべき最強の言語のひとつです。 ``` require! <[async sqlite3]> db = new sqlite3.verbose! .Database \db.sqlite3 err, friends <- db.all "SELECT * FROM friend" err, messages <- db.all "SELECT * FROM message" friends.for-each ({user_id, phase})-> object.send-message messages.phase.(phase), user_id ``` ついにネストがなくなりました。 LiveScriptのバックコールという機能を利用して`<-`という書き方をすると、 JavaScriptに変換する際に最終引数にコールバックとして配下の行を登録するという記述になります。 その他細々した箇所が格段に書きやすくなっており、 LiveScriptに慣れた人からすればCoffeeScriptなど冗長で退屈な制約に縛られた言語に見えます。 使い方もまったくといっていいほどCoffeeScriptと同様です。 ``` $ npm install -g livescript $ lsc send_messages.ls ``` ``` require("livescript"); require("./send_messages.ls"); ```