結論から言いますが、
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を勉強してみてください。