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

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

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

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

Node.js

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

JavaScript

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

Q&A

解決済

1回答

709閲覧

ユーザ名がUniqueか検証したい[Node.js + MySQL]

rebel_nit

総合スコア13

MySQL

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

Node.js

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

JavaScript

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

0グッド

0クリップ

投稿2018/12/14 07:45

編集2018/12/14 17:26

前提・実現したいこと

Node.jsもMySQLも初心者で、勉強もかねてブログを作ってます。
入力されたusernameが既にDB内に存在するか検証したいのですが、うまく動きません。

###想定している動作例
HPにはusernameとpasswordを入力するformと送信ボタンを用意する。(URLはlocalhost:3000/signup)
送信ボタンを押すとlocalhost:3000/signup/validateに遷移。

usernameとpasswordが入力されなかった場合、isFormFilledの時点でisValidatedがfalseになって、errorsを返す。
usernameとpasswordが入力されているが、そのusernameを持つデータがDBに既に存在したらisValidatedがfalseになって、errorsを返す。
そのusernameをもつデータが存在しない場合、それをDBに登録する。(未実装)

質問

  • checkStatus関数を使用して、usernameとpasswordが空の場合、errorsにエラーが、isValidateもfalseが入っていることは確認できました。。

しかし、既にDBに登録されているusernameと適当なパスワードを入力した場合、errorsにエラーは入るものの、isValidatedがtrueのままになっていました。
isValidatedもfalseになってほしいのですが、何故trueのままなのでしょうか?

  • account.jsに関して、originalとerrorsの代入が終わる前にconsole.logs(errors)が実行されてしまうのですが、どうすれば順番に処理されるのでしょうか?

該当のソースコード

account-control.js

js

1var { MYSQL_PASSWORD } = require("../../config/app.config").security; 2var mysql = require("mysql"); 3var connection = mysql.createConnection({ 4 host: "localhost", 5 user: "root", 6 password: MYSQL_PASSWORD, 7 database: "bbs" 8}); 9 10class Authenticator { 11 static createRegistUsersData(body) { 12 return { 13 username: body.username, 14 password: body.password 15 }; 16 } 17 18 static validateRegistUsersData(body) { 19 var isValidated = true, errors = {}; 20 21 var isFormFilled = () => { 22 return new Promise((resolve, reject) => { 23 if (!body.username) { 24 isValidated = false; 25 errors.username = "usernameが未入力です。"; 26 } 27 if (!body.password) { 28 isValidated = false; 29 errors.password = "passwordが未入力です。"; 30 } 31 32 if (isValidated === false) { 33 reject(); 34 } else { 35 resolve(); 36 } 37 }); 38 }; 39 40 var isUsernameUnique = () => { 41 return new Promise((resolve, reject) => { 42 if (body.username && body.password) { 43 Promise.all([() => { 44 connection.connect(); 45 connection.query({ 46 sql: "SELECT * FROM users WHERE username = ?;", 47 values: [body.username] 48 }, (error, results, fields) => { 49 if (results == "") { 50 console.log("unique"); 51 resolve(); 52 } else { 53 errors.username = "usernameは既に使用されています。"; 54 isValidated = false; 55 reject(); 56 } 57 }); 58 }, () => { 59 connection.end(); 60 }]); 61 } 62 }); 63 }; 64 65 var checkStatus = () => { 66 return new Promise((resolve, reject) => { 67 console.log(errors); 68 console.log(isValidated); 69 resolve(); 70 }); 71 }; 72 73 Promise.resolve() 74 .then(isFormFilled()) 75 .then(isUsernameUnique()) 76 .then(checkStatus()) 77 .then(() => { 78 return isValidated ? undefined : errors; 79 }); 80 } 81} 82 83module.exports = Authenticator;

account.js

js

1var router = require("express").Router(); 2var Authenticator = require("../lib/security/account-controll"); 3 4 5router.get("/", (req, res) => { 6 res.render("./account/signup.ejs", { message: req.flash("message") }); 7}); 8 9router.post("/validate", (req, res) => { 10 var original, errors; 11 Promise.resolve() 12 .then(() => { 13 original = Authenticator.createRegistUsersData(req.body); 14 errors = Authenticator.validateRegistUsersData(req.body); 15 }) 16 .then(() => { 17 console.log(errors); 18 if (errors) { 19 res.render("./account/signup.ejs", { original, errors }); 20 } 21 else { 22 res.render("./account/regist-complete.ejs"); 23 } 24 }); 25}); 26 27 28 29module.exports = router;

###追記
1.staticなメソッドをasyncさせる方法はないのでしょうか?
2.以下のvalidateRegisterUsersData()のようなPromiseチェーンを組むときに3変数になってしまうのですが、isFilledUsernameの引数をusernameだけにしてもPromiseチェーンは組めるのでしょうか?
3.以下の2点を守ってコードを書き換える際に、Promiseチェーンを組むときは以下のような形になると思ったのですが、間違ってますか?

  • ejsにエラー情報を渡したいので、errorsの中にエラーを格納したい。
  • クラスを崩して、すべてのメソッドをモジュールとしてexportsするのは避けたいので、Authenticatorクラスの関数として実装したい。

js

1 static validateRegistUsersData(body) { 2 console.log("validateRegistUsersData"); 3 var isValidated = true, errors = {}; 4 Promise.resolve(body.username, isValidated, errors) 5 .then(this.__isFilledUsername) 6 .then((results) => new Promise((resolve, reject) => { 7 resolve(results.isValidated); 8 })); 9 } 10 11 12 // __isFilledUsername = username, isValidated, errors => new Promise() だとエラー 13 __isFilledUsername(username, isValidated, errors) { 14 console("__isFilledUsername"); 15 new Promise((resolve, reject) => { 16 console.log(username); 17 resolve({ isValidated, errors }); 18 }); 19 } 20 21 static __isFilledPassword(password, isValidated, errors) { 22 new Promise((resolve, reject) => { 23 isValidated = false; 24 errors.password = "passwordが未入力です。"; 25 }); 26 }

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

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

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

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

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

guest

回答1

0

ベストアンサー

実践ではMySQLはコネクション張りっぱなしですし、
その位置でPromise.allは意味無いですね。
あと、1メソッドの中身が何行あるんだ?ってくらいあってオブジェクト指向の意味が無かったりしますが・・・

まぁ、これらは本件に関係ないので後々の課題ということで気が向いたらぼちぼち改良させてください。

if (results == "") {

!?!?!?

JSの比較演算子は挙動の管理がクソ面倒なので、
TeratailのNode.jsランキングで1位を取るまではイコール2つ==の比較演算子は使用してはいけません。
唯一使っても良い例外はnull,undefinedを両対応で見分けるresults == nullだけです。

まぁ、[] == ""の結果はtrueになりますし、[{}] == ""の結果はfalseになる事を確認したので大丈夫ですが。

isValidatedもfalseになってほしいのですが、何故trueのままなのでしょうか?

全コードを舐めましたが、Node.js名物非同期処理の洗礼ですね。
Node.jsはシングルスレッドで動く関係で、普通のコールバック地獄を嫌がってPromiseで包んでも別に同期処理になるわけではないので、質問文の課題は解決しません。

この辺はNode.jsのイベントループの仕様を勉強してみてください。
つまり、一生thenの中身で数珠繋ぎにして後続の処理を解決させてください。

JavaScript

1Promise.resolve() 2 .then(isFormFilled()) 3 .then(isUsernameUnique()) 4 .then(checkStatus()) 5 .then(() => { 6 return isValidated ? undefined : errors; 7 });

君たち、そこで実行しちゃダメだよ。
.then(isFormFilled)のようにカッコ無しで繋ぐのが正式な使い方です。

Promise内でresolve('hogehoge')みたいに値を入れて実行して、
次のthenの第一引数に突っ込んだ関数の更に第一引数にこの'hogehoge'というデータを入れて実行させるというリレーをする為のものです。

つまり、コードとしてはほぼ惜しい所までは出来ているものの、
実際にこれを走らせるとthenの意味はまるでなくて、その場で全てのPromiseが同時に走るわ、引き継がせるデータがちゃんとリレー出来てないわという有様で、
意図したように動かないんじゃないの?という印象ですね。

特に設計思想としてはpromise走らせた結果としてresolve(isValided)を実行しながら返すという設計にすべきで、予めlet isValided = true;を実行しておいて、Promiseの中身で外のスコープの変数を書き換えてfalseにするのはお前Promiseの意味ないじゃんということで落第です。

どうすれば順番に処理されるのでしょうか?

Promise覚え直しですね。
とはいえ、thenの中に全部書けというのは相当辛いのでアドバイスします。
async/await覚えましょう。

トップレベルにmainみたいな関数用意して、こんな感じでやれば綺麗に書けるでしょう。
どの様にオブジェクト指向プログラミングをしていくかは別途考えてみてください。

JavaScript

1// Node.jsがWebサーバであるならMySQLは繋ぎっぱなしにすべき 2connection.connect(); 3 4// 最初の1行目で返すなら{}もreturnも不要でこの様に書いたほうがいい 5// 関数は外のbodyみたいな値に依存するのではなく、引数で受け付けたほうがいい 6const isUniqueUsername = username => new Promise((resolve, reject) => { 7 // usernameが空か否かはisUniqueで確認するべきじゃないので、バリデート関数を別途用意すべき 8 9 connection.query({ 10 sql: "SELECT * FROM users WHERE username = ?;", 11 values: [username] 12 }, (error, results, fields) => { 13 // errorがnull以外の時は、MySQLのコネクション失敗やらの申告なエラーなのでrejectで対応 14 if (error) { 15 reject(error); 16 return; 17 } 18 19 // 余計な事はせずにやること終わったらさっさとtrue or false返して終了する 20 resolve(results.length === 0); 21 }); 22}); 23 24const main = async () => { 25 // bodyをどっから取ってくるかは知らんけどこんな感じ 26 var isUnique = await isUniqueUsername(body.username); 27 if (isUnique) { 28 console.log('ユニークなIDです'); 29 } else { 30 console.log('IDが重複しています'); 31 } 32}

【追記箇所への回答】

1.staticなメソッドをasyncさせる方法はないのでしょうか?

まぁ、参考書とかもないですし情報少なくて辛いですよね。
これを参考にしてみてください。

※await構文の右辺がPromiseの場合、実行してresolve(value)のvalue部分を取り出しますが、String等の値であればそれをそのまま利用しようとします。

JavaScript

1class Animal { 2 static async meow () { 3 return await 'meow!' 4 } 5} 6Animal.meow().then(console.log); 7// "meow!"が出力される

2.以下のvalidateRegisterUsersData()のようなPromiseチェーンを組むときに3変数になってしまうのですが、isFilledUsernameの引数をusernameだけにしてもPromiseチェーンは組めるのでしょうか?

できる or できないで言えばできます。
Promiseのreslve関数を実行するときの書式で、resolve(1, 2, 3)という風に3つの引数で実行した場合、
.then((a, b, c) => console.log(a, b, c))という風に3つの引数で受けることが可能です。

ただし、async / await構文で戻って来る場合、resolveの第一引数しか持って帰れません。
Promise返す関数は最小の挙動を記述すれば、そんなにたくさんの引数を持って帰りたいという要求は出るはずがありません。

3.以下の2点を守ってコードを書き換える際に、Promiseチェーンを組むときは以下のような形になると思ったのですが、間違ってますか?

そもそもの話で、Promiseが必要な関数/メソッドってどういうものだと思いますか?
それは「非同期処理であるべきもの」だけです。

非同期処理であるべきものとは何でしょうか?
それは主に「HDDに問い合わせる」「ネットワーク越しに問い合わせる」といった、メモリに保存済みのものを呼び出すものより圧倒的に遅いものがその対象になるべきです。

JavaScript

1static __isFilledPassword(password, isValidated, errors) { 2 new Promise((resolve, reject) => { 3 isValidated = false; 4 errors.password = "passwordが未入力です。"; 5 }); 6}

passwordが変数が空か否かを確認するメソッドは非同期処理であるべきか?
と聞かれれば、別にHDDやネットワーク越しに問い合わせるわけではないので不要です。

要するにお前Promiseである必要ないじゃんって話ですし、
isFilledPasswordというメソッド/関数ならば、普通のエンジニアはtrue or falseが帰って来ることを想定するはずです。
この辺のメソッド名の名付けや挙動は英語力も必要になってきます。

Authenticatorクラスの中の話なので、例えばinvalidReasonsという配列が帰ってきそうなメソッド名にしてはいかが?
配列が空、つまりreasons.length === 0がtrueならば妥当なデータ、falseならば不正な値というわけです。
これなら死ぬほど使いやすくなったと思いませんか?

JavaScript

1class Authenticator { 2 static invalidReasons({username, password}) { 3 const reasons = []; 4 if (username === '') { 5 reasons.push('usernameが未入力です'); 6 } 7 if (password === '') { 8 reasons.push('passwordが未入力です。'); 9 } 10 return reasons; 11 } 12}

isValidated = false;

細かいですが、こういう使い方はバグの原因なので極力やめてください。
こういう使い方する関数を「副作用がある関数」と呼び、一定以上の技量のエンジニアには嫌われる使い方です。
パフォーマンス改善のテクニックの一つではあるのですが、これを意識せず使いまくったシステムだと単純にバグの量が100倍増えるでしょう、それだけのコストを払う意味があるケースは実際殆ど存在せず禁じ手になっています。

また、今回の用途では正しく動作しません。
何故かというとString、Number、Booleanなどのプリミティブ値は値渡しなので、関数の内側で引数として宣言したisValidated変数の内容は変わりますが、引数として渡した外側の世界の変数には影響しません。

JavaScript

1var add3 = it => { 2 it += 3; 3 return it; 4} 5var a = 1; 6console.log(add3(a)); // 4 7console.log(a); // 1

オブジェクトや配列等のオブジェクト系はJavaScriptでは一見参照渡しのように思えますが、
実際には参照の値渡しなので、やはり束縛が切れるので無意味です。

JavaScript

1var nameChange = (obj, name) => { 2 obj = {name}; 3 return obj; 4} 5var taro = {name: 'taro'} 6var jiro = nameChange(taro, 'jiro') 7console.log(taro); // {name: 'taro'} 8console.log(jiro); // {name: 'jiro'} 9 10// 副作用を起こしたければこうする 11var nameChange2 = (obj, name) => { 12 obj.name = name; // 変数への代入ではなく、プロパティを書き換えるような挙動にする 13 return obj; 14} 15var taro = {name: 'taro'} 16var jiro = nameChange2(taro, 'jiro') 17console.log(taro); // {name: 'jiro'} <- 副作用の再現に成功 18console.log(jiro); // {name: 'jiro'}

// __isFilledUsername = username, isValidated, errors => new Promise() だとエラー

ES2015のクラス構文の書き方を復習しましょう
クラス - MDN

それと、アロー関数のルールでusername, isValidated, errors => new Promise()のように引数が複数になる場合は引数の部分のカッコは省略できません。
アロー関数でカッコを省略できるのは引数が1個のケースだけです。

アロー関数 - MDN

投稿2018/12/14 09:21

編集2018/12/15 04:55
miyabi-sun

総合スコア21158

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

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

rebel_nit

2018/12/14 17:28

解答ありがとうございました! 色んなサイトを見ながら見様見真似で書いたので、以下に自分がきちんと理解できていないかを実感しました。 Promiseやawaitの勉強を少ししたうえで書いてみたのですが、もしよければ追記にもお答えしていただけるととても助かります!
rebel_nit

2018/12/15 09:13

追記への解答ありがとうございました。 とても丁寧に答えていただけたので、どこの理解が足りないのかというのがわかって調べやすかったです! Node.jsも少しずつ勉強していくので、また困ったことが出てきた時に助けていただけると嬉しいです!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問