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

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

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

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

Node.js

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

Express

ExpressはNode.jsのWebアプリケーションフレームワークです。 マルチページを構築するための機能セットおよびハイブリッドのWebアプリケーションを提供します。

Q&A

解決済

2回答

1640閲覧

Node.js+expressでmysqlからのデータをreturnしたい

jobc

総合スコア32

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

Node.js

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

Express

ExpressはNode.jsのWebアプリケーションフレームワークです。 マルチページを構築するための機能セットおよびハイブリッドのWebアプリケーションを提供します。

0グッド

0クリップ

投稿2022/04/02 00:38

現在、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ページで確認できます。

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

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

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

guest

回答2

0

ベストアンサー

出来ますよ。
しかし、それを理解して実現するまでは長い道のりになります

上から順番に勉強していき、Promiseの理解を深めてasync/await構文を使いこなせるようになればゴールです。
まぁ、リンクを上から開いて眺める程度で理解できれば誰も苦労しない分量の話ですので、
数ヶ月スパンでゆっくり理解を深めてください。

それは流石に勘弁して欲しいと思うので、
今回のゴールとしてどうすれば良いのかを含め、ざっくり概要を解説していきます。


JavaScriptはブラウザで描画しているHTML(DOMツリー)を
後から編集して画面の更新を促す為の言語です。

「後から」ってなんやねん?
「ボタンをクリックした時」「スクロールした時」「画像の上にマウスカーソルが乗った時」
こういう契機をトリガーにJavaScriptの処理が走って欲しいわけです。

これを実現する為のアプローチとして
「シングルスレッド」「イベントループ」という2つの特徴を利用しています。
シングルスレッドは今回の話には関係ないので端折ります。


「イベントループ」の解説します。
「イベント置き場」が用意されており、「達成条件」と「実行して欲しい処理を関数として包んだもの」の2つをセットで登録します。
この「イベント登録」処理自体は「わかった、後で条件確認して、達成したのを確認できたら実行しておくね」と受理されるだけで実行せずに流します。

その後JavaScriptが持っている処理が全て終わって手が空いたら
登録されているイベントの「達成条件」をクリアしたものが無いか巡回して探し始めます。
そして「達成条件」をクリアしていたら、セットになっている「関数」を取り出して実行。
これを繰り返す事を「イベントループ」と呼びます。

イベント登録を行う為には達成条件だけではなく、実行して欲しい関数も必要です。
JavaScriptは関数を「変数に保存したり関数実行時の引数に出来る」という特徴があります。
この特徴を使って関数を作ってあちこちに運んで、必要に応じて実行して使うみたいな事が出来るんですね。

こういう使い方をする関数を「コールバック」と呼びますが、
何のことはない関数実行時の引数として関数作って渡してるだけです。


既存のコードはわかった、
イベントループやコールバックを利用して作られてるんだな。

コールバックの制約の話をしなければなりません。
こいつらreturntry-catchthrowをすり抜けます。

先程私はこのように説明しました。

「イベント登録」処理自体は「わかった、後で条件確認して、達成したのを確認できたら実行しておくね」と受理されるだけで実行せずに流します。

イベント登録ではコールバック関数を実行せず流してしまい、正常終了します。
そしてイベントループ中のよくわからん文脈で関数は実行されます。
つまり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

miyabi-sun

総合スコア21400

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

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

jobc

2022/04/02 07:00

わざわざ解説までしていただいてありがとうございます。参考にさせていただきます。
guest

0

Node.jsに詳しくないので間違っているかもしれませんが、関数の引数にconnectionを渡す必要があるのではないでしょうか?

投稿2022/04/02 00:43

mineralwater

総合スコア289

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

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

jobc

2022/04/02 00:49

回答ありがとうございます。 やってみましたが何も変わりませんでしたね..
jobc

2022/04/02 06:55

ありがとうございます。参考にします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.31%

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

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

質問する

関連した質問