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

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

新規登録して質問してみよう
ただいま回答率
85.35%
Node.js

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

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

2回答

3184閲覧

Node.jsでMySQLのレコードデータを取得後、変数に格納できません

kobo_jp

総合スコア19

Node.js

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

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2021/05/28 02:15

問題

Node.jsでWebサーバーを作成しています。
MySQLから読み込んだレコードデータをサイト上に表示させるため実験中なのですが、
SELECT文で取得したデータを変数にうまく格納することができません。

undefined

該当のソースコード

nodejs

1const mysql = require('mysql2'); 2 3const db = mysql.createConnection({ 4 host: 'localhost', 5 user: 'user1', 6 password: 'password1', 7 database: 'db1' 8}); 9 10db.connect( (error) => { 11 if (error) { 12 console.log(error); 13 } else { 14 console.log('MySQL Connected...'); 15 } 16}); 17 18let name; // (1) 19 20// テーブルmytableの最初レコードのカラムnameには'Yamada'が入力されている 21db.query('SELECT * FROM mytable', (error, results) => { 22 name = results[0].title; // (2) 23 console.log(name); // 'Yamada'とコンソールに表示...(3) 24}); 25 26console.log(name); // 'undefined'とコンソールに表示...(4) 27 28/* コンソール 29undefined 30MySQL Connected... 31Yamada 32*/ 33

補足情報

期待しているのは、上記コードの変数nameに(1)、
MySQLのテーブルから取得した'Yamada'が格納され(2)、
(4)のタイミングで'Yamada'と表示されることですが、
(1) -> (4) -> (2) -> (3)の順に実行されているようで、
(4)では'undefined'となります。

この問題はどのように解消すればよいのでしょうか。
基本的なことがわかっていないためとは思いますが、
どうぞよろしくお願いいたします。

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

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

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

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

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

hoshi-takanori

2021/05/28 02:44

非同期処理とはそういうものです。
kobo_jp

2021/05/28 02:50

そういうものですか。
hoshi-takanori

2021/05/28 04:56

db.query 自体は、データベースにクエリーを投げたら結果を待たずにすぐに return します。ので (4) が先に表示されます。その後、結果が返ってきてから db.query の引数で渡したアロー関数の (2) と (3) が実行されます。 なお、mysql2 をお使いなら async/await に対応してるので、それを使うと幸せになれるかも。 https://thr3a.hatenablog.com/entry/20200817/1597650222
kobo_jp

2021/05/28 06:47

私もその後ご指摘のサイトにたどり着き、試したところでした。 promiseとasync/awaitの使い方がよくわかっていなかったので助かりました。 これから解決法を簡単に書いてみようかと思います。 どうもありがとうございました。
guest

回答2

0

結論から言いますが、
Promiseとawait/asyncの使い方を学習して、
mysql2のPromise版を扱いましょう。

参考記事: Express,mysql2/promiseでasync/await使って簡潔に書く - qiita

これにより質問文のコードは下記のようなコードに置き換えられるでしょう

js

1// 参照先をこう改良する 2const mysql = require('mysql2/promise'); 3 4const db = mysql.createConnection({ 5 host: 'localhost', 6 user: 'user1', 7 password: 'password1', 8 database: 'db1' 9}); 10 11db.connect( (error) => { 12 if (error) { 13 console.log(error); 14 } else { 15 console.log('MySQL Connected...'); 16 } 17}); 18 19// await演算子で処理を待つにはasync関数内という制約があるので関数定義 20const main = async () => { 21 try { 22 // await演算子を使う事でPromise.thenで値を取り出したのと同じ効果を得られる 23 const results = await db.query('SELECT * FROM mytable'); 24 25 const name = results[0].title; // (2) 26 console.log(name); // 'Yamada'とコンソールに表示...(3) 27 28 console.log(name); // 'Yamada'とコンソールに表示...(4) 29 } catch (e) { 30 // Promiseをawaitで待つとtry-catchで拾うことが可能 31 console.error(e); 32 } 33} 34 35main();

js

1// テーブルmytableの最初レコードのカラムnameには'Yamada'が入力されている 2db.query('SELECT * FROM mytable', (error, results) => { 3 name = results[0].title; // (2) 4 console.log(name); // 'Yamada'とコンソールに表示...(3) 5}); 6 7console.log(name); // 'undefined'とコンソールに表示...(4)

JS(Node.js)流儀の「イベントループ」の仕様上そうなります。
詳しくは「イベントループ」の勉強をしてくださいという感じですが、
理解しやすいよう多少噛み砕いて解説します。

JS(Node.js)にはイベント置き場というものがあり、
そこに達成条件と後で実行して欲しい処理を関数で包んだものを渡します。
これでイベント登録申請を行います。

全ての処理が終了して手待ちになった時、
「イベント登録申請」が行われていたら「そういやイベント登録されてたな?」と巡回します。
その巡回中に達成条件を満たしたイベントがあれば、対になっている関数を実行します。

そして「イベント登録申請」は「後でイベント達成した事を確認したらやっておいてね」という約束にほかなりません。
なので、4→3の順番で実行される事が決まっています。

イベント登録を挟んだコードの実行が遅延される事は
下記のコードで確認出来ますね。

js

1setTimeout( 2 () => console.log(1), 3 0 // 0ミリ秒に満たした時に発火、実行した瞬間条件は達成しているはずである 4); 5console.log(2); 6 7// 2 -> 1の順番で出力される

じゃあどうすれば良いのか?

それは上のコールバック関数に以後やるべき事を全て書けという事になります。

js

1setTimeout( 2 () => { 3 console.log(1); 4 5 // 以降の処理全部をここに書け! 6 console.log(2); 7 }, 8 0 9); 10

いやいや、そんなことしたら
非同期処理を行う度に無名関数宣言→インデントが作られて
インデントだらけのクソみたいなコードになっちゃうでしょ?

なりますね。
これを「コールバック地獄」と呼びます。
多くのWebエンジニアを長い事困らせる原因になっていました。

そこでこのコールバック地獄を解消する為に2段階での技術の導入が行われる事に成ります。
それはES2017というJavaScriptの2017年度版仕様で導入済みであり
その仕様はNode.jsにも取り込まれています。

要するにこの書き方や課題は既に大昔の話なので、
新しいやり方に追従しましょう。


ES2015でまずPromiseオブジェクトが用意されます。

上記のコールバック地獄をオブジェクト指向言語のテクニックを駆使して
関数をメソッドで包んで管理すればネストは1個に押さえ込めるよね?
という発想からきています。

質問文のコードはこうなります。

js

1const mysql = require('mysql2'); 2 3const db = mysql.createConnection({ 4 host: 'localhost', 5 user: 'user1', 6 password: 'password1', 7 database: 'db1' 8}); 9 10db.connect( (error) => { 11 if (error) { 12 console.log(error); 13 } else { 14 console.log('MySQL Connected...'); 15 } 16}); 17 18let name = null; 19 20new Promise((resolve, reject) => { 21 db.query('SELECT * FROM mytable', (err, results) => { 22 // Promise流儀の結果を加工するやり方で終わらせる 23 if (err) { 24 reject(err); 25 return; 26 } 27 resolve(results); 28 }); 29}).then(results => { 30 name = results[0].title; 31}).then(() => { 32 console.log(name); // 'Yamada'とコンソールに表示...(3) 33}).then(() => { 34 console.log(name); // 'Yamada'とコンソールに表示...(4) 35})

コードの動作は確認していませんが、まぁ動くでしょう。
Promiseをnewで作って.then(fn)メソッドを数珠つなぎに繋げています。

上の処理が終わったら次のthenメソッドで登録した関数がロードされて実行されます。

Promiseは途中で変数に保管できますので、
続きを後からthenメソッド実行で書き足す事も可能です。
for文で100個のSQLを投げる事もシンプルに記述出来るようになります。

既存のコールバックを使った非同期処理はfor文を使ってこういう事をやることは不可能レベルに困難でしたから
大きな一歩と呼んでも差し支えない大偉業です。

ですが、これでもまだ不便は不便。
やりたい事がPromise流儀のthenだらけになって読み辛いですよね?
JSの開発者達はさらなる改良を行います。


それがasync/awaitです。
ES2017で仕様が確定され、新しいブラウザやNode.jsに取り込まれました。

これはコード上のPromiseの気持ち悪い書き方を
内部に押し込め、コードを記述するプログラマーにとって読み書きを楽にするための方法です。

関数を宣言する時、async関数を定義することが出来ます。
これは普通のコードをPromise的なコードで無理やり置き換えます

js

1// () => new Promise(fn) と同じコードになり、 2// 結果は必ずPromiseのインスタンスである 3async () => { 4 await 123; // await構文でPromise.then(fn)のように待つ事が可能 5 throw "失敗"; // return reject("失敗")と同じコード 6 return 123; // return resolve(123)と同じコード 7}

このようにasync関数の内部をゴリ押しで変換して解決している状態なので、
Promiseの正しい理解がないと上手く使いこなせないはずです。

という訳でasync/awaitを使いこなす事で、
回答文頭のコードに落とし込む事が可能となります。

全て解説し始めると日が暮れるので、
コレを機にasync/awaitを勉強してみてください。

投稿2021/05/28 06:51

miyabi-sun

総合スコア21203

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

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

kobo_jp

2021/05/28 07:14

とても親切丁寧なご説明とコードありがとうございました。 ぼんやりしていたものがくっきり見えてきた感じです。 本当にありがとうございました。
guest

0

自己解決

解決しました。
こんな感じで同期できるようです。
(1) -> (2) -> (4)の順に実行されます。

nodejs

1const mysql = require('mysql2/promise'); 2 3(async () => { 4 const db = await mysql.createConnection( 5 { 6 host:'localhost', 7 user: 'user1', 8 password: 'password1', 9 database: 'db1' 10 } 11 ); 12 13 let name; // (1) 14 15 // テーブルmytableの最初レコードのカラムnameには'Yamada'が入力されている 16 const [rows, fields] = await db.query('SELECT * FROM toppage'); 17 name = rows[0].name; // (2) 18 19 console.log(name); // 'Yamada'とコンソールに表示...(4) 20})();

投稿2021/05/28 07:04

kobo_jp

総合スコア19

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

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

miyabi-sun

2021/05/28 08:01

修正欄でのアドバイスがあったとはいえ、 自力でここまでたどり着けたのは素直に素晴らしいです。 頑張ってください。
kobo_jp

2021/05/28 11:43

いえいえ、未だサンプルがないとまともにコードが組めないのは情けない限りです。 Node.jsは奥が深いですが、習得するとかなり良いものが作成できそうなので頑張りがいがあります。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問