質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
SQLite

SQLiteはリレーショナルデータベース管理システムの1つで、サーバーではなくライブラリとして使用されている。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Q&A

解決済

2回答

5889閲覧

[node.js][sqlite3] データベースを読み込んで処理をするときに綺麗なコードが書きたい

horik

総合スコア44

SQLite

SQLiteはリレーショナルデータベース管理システムの1つで、サーバーではなくライブラリとして使用されている。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

0グッド

0クリップ

投稿2016/11/12 05:43

Friendテーブル
user_id=10001, phase=1
user_id=10002, phase=3
user_id=10003, phase=2

Messageテーブル
phase=1, body='こんにちわ'
phase=2, body='○○に住んでいる○○です'
phase=3, body='よろしくお願いします'

sqliteで上のようなテーブルがあったとして
これを読み込んでfriendからメールが来た時に
Friendテーブルのphaseに応じて
Messageテーブルの文章を
sendMessage(body, user_id);
という形で送信したいと思っています。

sqlで読み込むときにどうしてもコールバック地獄になって
綺麗にコードが書けません。
なにかアドバイスお願いします。

試したこと

JavaScript

1var async = require('async'); 2var sqlite3 = require("sqlite3").verbose(); 3var db = new sqlite3.Database('db.sqlite3'); 4 5async.waterfall( 6 [ 7 function(callback) { 8 db.all("SELECT * FROM friend", function (err, friends) { 9 callback(null, friends); 10 }); 11 }, 12 function(friends, callback) { 13 db.all("SELECT * FROM message", function (err, messages) { 14 callback(null, messages); 15 }); 16 }, 17 function(messages, friends, callback) { 18 for(var i = 0; i < friends.length; i++) { 19 body = messages['phase'][friend[i]['phase']]; 20 user_id = friends[i]['user_id']: 21 object.sendMessage(body, user_id); 22 } 23 callback(null); 24 }, 25 ] 26);

かなり簡略化していますが、
毎回こんな感じでasyncを使ってやっていかなくてはならないんでしょうか?
やりたいことを実現できるライブラリがnode.jsにしかなくて仕方なく使っています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

coの使用をお勧めします。

JavaScript

1const co = require('co'); 2const thunkify = require('thunkify'); 3 4co(function*(){ 5 const sqlite3 = require("sqlite3").verbose(); 6 const db = new sqlite3.Database('db.sqlite3'); 7 const db_all = thunkify(db.all.bind(db)); 8 9 const friends = yield db_all("SELECT * FROM friend"); 10 const messages= yield db_all("SELECT * FROM message"); 11 12 for(const friend of friends){ 13 const body = messages['phase'][friend['phase']]; 14 const user_id = friend['user_id']: 15 object.sendMessage(body, user_id); 16 } 17 18 return 'success'; 19}).then(console.log);

coを使用するとこのように書く事ができます。
スクラッチで記載しましたのでSyntaxエラーになる可能性はありますが、
コードの見た目は概ね変わらないでしょう。

coの詳しい使用方法はcoのサイト等でご自身で調べて下さい。

注意点としてes6の機能を使用するため、es6を使用できない古いnodeでは使用できません。

投稿2016/11/12 13:41

編集2016/11/12 13:50
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

horik

2016/11/12 23:10

ブロッキングで順次読み込んで処理できるんですね。 ちょっと試してみます。 AltJSと併用もできるんですよね。
guest

0

ベストアンサー

Node.js・JavaScriptのシングルスレッドでパフォーマンスを上げるために、
コールバック関数を利用したノンブロッキングなフローという事で根本的な解決は不可能です。
コールバック地獄は避けられません。

小細工的なところでAsyncとPromiseの2通りの逃げ方ができますが、どちらも大差ないでしょう。

対策その1:処理を関数化して細分化する

そのまんまです。
Node.jsで増えたcommon.jsの機能であるrequireをフル活用すればもう少しきれいになると思います。

対策その2:AltJSに乗り換える

どんなに頑張ってもJavaScriptは所詮JavaScriptです。
CoffeeScriptやTypeScriptといったAltJSの名前を聞いたことはありませんか?

ほら、あのコンパイルするとJavaSciriptに変換されるメタ言語のことです。
raccyさんの記事が参考になるかと思います。
http://qiita.com/raccy/items/fae9fc5923d78112d935

よくNode.jsで大規模開発する場合、型がないので次第につらくなる、型の強固さを得たTypeScriptに移ろうという話はありますがとんでもない!
大規模開発しない人はすべからくJavaScriptを捨ててCoffeeScript、もしくはそれの進化形であるLiveScriptへ移行すべきです。

ちょっとCoffeeScriptで上記のコードを書いてみますね。
コード量が半分くらいで収まるので刮目してみてください。
あ、因みにfor文はバグの温床なのでArray.forEachに書き換えています。

CoffeeScirpt

1async = require('async') 2sqlite3 = require("sqlite3").verbose() 3db = new sqlite3.Database('db.sqlite3') 4 5db.all "SELECT * FROM friend", (err, friends)-> 6 db.all "SELECT * FROM message", (err, messages)-> 7 friends.forEach (friend)-> 8 body = messages.phase[friend.phase] 9 user_id = friend.user_id 10 object.sendMessage body, user_id

CoffeeScriptはJavaScriptにRubyやPythonのエッセンスを少し加えた程度の言語なので、
horikさんの腕なら何となくでも読めるはずですし、書けるはずです。
変換後の正確なJavaScriptコードは公式サイトに行けば変換できます。

コールバック地獄の問題の本質は、ネストや波括弧・丸括弧だらけになりロジック自身が見えづらいことです。
CoffeeScriptでfunctionや括弧という決まり事のノイズをそぎ落として、
コアなロジック部分だけ表示することで各段に読みやすくなっているかと思います。

ある程度のレベルならAsyncやPromiseという小技に頼る必要がなくなります。
しかも使い方は超絶簡単、上記ファイルを「send_messages.coffee」として保存した前提で実行してみますね。

Bash

1$ npm install -g coffee-script 2$ coffee send_messages.coffee

なんと実行する際のnodeコマンドがcoffeeコマンドに変わっただけです。
AWSのLambdaのようなネイティブのNode.jsしか使えない環境でも、こんな感じのJSファイルから実行できます。

JavaScript

1require('coffee-script'); 2require('./send_messages.coffee');

いかがでしたでしょうか?
しかし、CoffeeScriptは時代遅れなのでLiveScriptを使いましょうね。
こちらは関数型言語のエッセンスを加えたJavaScript版Scalaとでもいうべき最強の言語のひとつです。

LiveScirpt

1require! <[async sqlite3]> 2db = new sqlite3.verbose! .Database \db.sqlite3 3 4err, friends <- db.all "SELECT * FROM friend" 5err, messages <- db.all "SELECT * FROM message" 6friends.for-each ({user_id, phase})-> 7 object.send-message messages.phase.(phase), user_id

ついにネストがなくなりました。
LiveScriptのバックコールという機能を利用して<-という書き方をすると、
JavaScriptに変換する際に最終引数にコールバックとして配下の行を登録するという記述になります。

その他細々した箇所が格段に書きやすくなっており、
LiveScriptに慣れた人からすればCoffeeScriptなど冗長で退屈な制約に縛られた言語に見えます。

使い方もまったくといっていいほどCoffeeScriptと同様です。

Bash

1$ npm install -g livescript 2$ lsc send_messages.ls

JavaScript

1require("livescript"); 2require("./send_messages.ls");

投稿2016/11/12 09:46

miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

horik

2016/11/12 22:55

かなり詳細な回答ありがとうございます! AltJSを調べてみました。 LiveScriptとTypeScriptが主流ぽいですね。 どうもLiveScriptは文法がRubyっぽくて自分には合わなそうです。 ぱっと見TypeScriptが魅力的に映るんですが 問題点がなければこちらを使いたいなと思います。
miyabi-sun

2016/11/13 00:54

残念、でも魅力に感じた方を使えば良いと思います。 AltJSは自分では実行できず、とにかくJavaScriptに変換して実行しますので全てのライブラリとJSと同じ書き方ができます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問