現在、Node.js + Express でMySQLを使って掲示板的webアプリを作っているんですが、関数でまとめたいと思い、
js
1var loadTable = function() { 2 connection.query( 3 'SELECT * FROM users', 4 (error, results) => { 5 if(error)throw error; 6 return results; 7 } 8 ); 9}
として、results
を戻り値にしたいんですが 、
js
1console.log(loadTable());
とすると
undefined
と出力され、どうやってもこうなるんで困っています。
関数にしないほうがいいんでしょうか?お願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答2件
0
ベストアンサー
出来ますよ。
しかし、それを理解して実現するまでは長い道のりになります
上から順番に勉強していき、Promiseの理解を深めてasync/await構文を使いこなせるようになればゴールです。
まぁ、リンクを上から開いて眺める程度で理解できれば誰も苦労しない分量の話ですので、
数ヶ月スパンでゆっくり理解を深めてください。
それは流石に勘弁して欲しいと思うので、
今回のゴールとしてどうすれば良いのかを含め、ざっくり概要を解説していきます。
JavaScriptはブラウザで描画しているHTML(DOMツリー)を
後から編集して画面の更新を促す為の言語です。
「後から」ってなんやねん?
「ボタンをクリックした時」「スクロールした時」「画像の上にマウスカーソルが乗った時」
こういう契機をトリガーにJavaScriptの処理が走って欲しいわけです。
これを実現する為のアプローチとして
「シングルスレッド」「イベントループ」という2つの特徴を利用しています。
シングルスレッドは今回の話には関係ないので端折ります。
「イベントループ」の解説します。
「イベント置き場」が用意されており、「達成条件」と「実行して欲しい処理を関数として包んだもの」の2つをセットで登録します。
この「イベント登録」処理自体は「わかった、後で条件確認して、達成したのを確認できたら実行しておくね」と受理されるだけで実行せずに流します。
その後JavaScriptが持っている処理が全て終わって手が空いたら
登録されているイベントの「達成条件」をクリアしたものが無いか巡回して探し始めます。
そして「達成条件」をクリアしていたら、セットになっている「関数」を取り出して実行。
これを繰り返す事を「イベントループ」と呼びます。
イベント登録を行う為には達成条件だけではなく、実行して欲しい関数も必要です。
JavaScriptは関数を「変数に保存したり関数実行時の引数に出来る」という特徴があります。
この特徴を使って関数を作ってあちこちに運んで、必要に応じて実行して使うみたいな事が出来るんですね。
こういう使い方をする関数を「コールバック」と呼びますが、
何のことはない関数実行時の引数として関数作って渡してるだけです。
既存のコードはわかった、
イベントループやコールバックを利用して作られてるんだな。
コールバックの制約の話をしなければなりません。
こいつらreturn
やtry-catch
やthrow
をすり抜けます。
先程私はこのように説明しました。
「イベント登録」処理自体は「わかった、後で条件確認して、達成したのを確認できたら実行しておくね」と受理されるだけで実行せずに流します。
イベント登録ではコールバック関数を実行せず流してしまい、正常終了します。
そしてイベントループ中のよくわからん文脈で関数は実行されます。
つまりtry-catch
はもう正常終了してしまっているので、その中でthrow
しても無駄ですし、
イベントループさんが関数実行しているので、何か値をreturn
で返しても誰も使わず捨てられてしまいます。
質問文のコードに注釈を足すとこうなります。
js
1var loadTable = function() { 2 // そもそも関数が値を返す作りではないので、returnを足した 3 return connection.query( 4 'SELECT * FROM users', 5 // この関数を引数として渡すのがコールバック、 6 // MySQLサーバとの通信後の結果を待つ為にイベントループを挟んでいるのだろう 7 (error, results) => { 8 if(error)throw error; // イベントループ内でのthrowなので、catchをすり抜けるので無駄 9 return results; // イベントループ内でのreturnなので、値を使いたい所は通り過ぎてて無駄 10 } 11 ); 12}
じゃあどうしなければならないんだ?というと、
コールバック関数の中に全部書くしかないんですよ。
js
1 return connection.query( 2 'SELECT * FROM users', 3 (error, results) => { 4 if(error)throw error; 5 console.log(results); 6 } 7 );
このコールバックの中で全部やらなければならない、
コールバックの数珠つなぎでカオスで悲惨な状況になってしまう惨状を「コールバック地獄」と呼びます。
いやいや、質問文に「関数でまとめたいと思い」って書いてるだろ。それじゃ意味がないだろ!ってなりますよね。
もし関数型プログラミングに慣れている人なら、
関数合成等のテクニックを駆使してそれなりにやりますが、
JavaScriptには関数型プログラミングをやるための機能・下地が不足している事もあって非常に難易度が高いです。
直感的に使いやすくて、
returnやtry-catchも効くようなのが欲しくないですか?
諸先輩の努力の結晶がPromiseであり、それを利用したasync/await構文です。
その辺良い感じに解説している記事を見つけたので紹介します。
参考記事: コールバック地獄からの脱出 - Qiita
Promiseに関しては後で頑張って調べてもらうとして、
async/awaitはPromiseを超楽に扱う糖衣構文です。
Promiseを扱える間はコールバックは不要ですし、
awaitでPromiseの状態をイベントループを回しながら完了になるまで待って値を取り出してくれたり、
try-catchで見張っている時にawait対象のPromiseの状態が失敗になったらcatchに流してくれるようになります。
なので、今使っているmysqlのモジュールが
もしPromiseのインスタンスを返すようにしてくれれば一気に視界が開けるのですが……
mysql2の場合はPromiseモードで駆動させる仕組みがあって普通に使えます。
もしmysqlモジュールを使っている場合は、開発者が逃げてmysql2という同じ動作をするがよりコードが洗練されているモジュール作ってるので移行しましょう。
js
1const mysql = require('mysql2/promise'); 2 3// await構文等を使う為に、async関数を宣言する必要がある 4// 適当にmain関数みたいなのを作って包む 5const main = async () => { 6 const connection = await mysql.createConnection({ 7 port: 3306, 8 user: 'testuser', 9 namedPlaceholders: true, 10 password: 'testpassword' 11 }); 12 13 // Promise版なのでconnection.queryの返り値がPromiseのインスタンスになる 14 const loadTable = () => connection.query('SELECT * FROM users'); 15 16 // awaitをつけると、Promise.thenを実行して引数の値を取り出して引き上げる事が可能となる 17 // その結果、あたかも同期処理のように結果を=で拾えるようになる 18 const result = await loadTable(); 19 console.log(result); 20 21 // awaitはこういう風に使っても良い 22 console.log(await loadTable()); 23 24 // try-catchもできるぞ 25 try { 26 console.log(await loadTable()); 27 } catch (e) { // Promise.catch(fn)を実行しているのと同じ事がこれで実現できる 28 console.error(e); 29 } 30} 31main();
mysql2のPromise版のexampleを確認してみてください。
投稿2022/04/02 04:45
総合スコア21400
0
Node.jsに詳しくないので間違っているかもしれませんが、関数の引数にconnectionを渡す必要があるのではないでしょうか?
投稿2022/04/02 00:43
総合スコア289
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2022/04/02 04:50

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2022/04/02 07:00