2023. 3. 27. 22:00ㆍ공부 중/Node.js
생활코딩 Node.js - MySQL 강의를 듣고서 작성한 글입니다. 그냥 그렇다고요.
1. 현재 상황
대충 이렇다.
겁나 복잡하다.
학습과 구현을 병행했기 때문에 아주 이상한 프로그램이 되었다.
보다 유지보수가 쉽도록 리팩토링을 수행한다.
2. 개선 방안
리팩터링의 핵심 포인트를 짚어보자.
가. DB 사용
template.js
에 위치한 메서드들이 readAndRes
를 거치지 않고 바로 DB에 접근할 수 있도록 해보자.
우선 db.js
를 만들어서 어디서든 db에 접속할 수 있도록 해주는 모듈을 만든다.
//db.js
const mysql = require("mysql2");
const db = mysql.createConnection({
host: process.env.MYSQL_HOST,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
port: process.env.MYSQL_PORT,
});
db.connect();
module.exports = db;
main.js
에 const db =require('./lib/db.js');
을 template.js
에 const db = require('./db.js);
를 추가한다.
가. 클래스
http get method
로 실행되는 작업들이 가지고 있는 패턴이 있다.
위와 같이 크게 4개의 값을 readAndRes
함수로 전달한다.
4개의 정보를 속성으로 가지는 클래스 Ready
를 생성하고 이를 상속해서 각각의 작업들이 수행되도록 해보자.
/** new Ready(title, des, control, author)
* 속성 title, description, contol, author, html
* method : makeHtml, response
*/
class Ready{
constructor(title, des, control, author){
this.title = title;
this.decscription = des;
this.control = control;
this.author = author;
this.html = '';
}
makeHtml() {
this.html = template.HTML(this);
}
response(){
response.writeHead(200);
response.end(this.html);
}
}
이에 맞춰서 template.js
도 수정한다.
//template.js
module.exports={
HTML: function (Ready) {
const list = this.List();
return `...생략...`;
},List: function () {
db.query(`SELECT * FROM topic`,(error, topics)=>{
if(error){throw error}
let list = "<ul>";
topics.forEach(topic => {
list += `<li><a href="/?id=${topic.id}">${topic.title}</a></li>`;
});
list += "</ul>";
return list;
});
},authorSelect:function(author_id){
db.query(`SELECT * FROM author`,function(error, authors){
if(error) throw error;
let tag = '';
authors.forEach(author => {
let selected ='';
if (author.id === author_id){
selected = 'selected';
}
tag += `<option value="${author.id}" ${selected}>${author.name}</option>`;
});
return `
<select name="author">
${tag}
</select>
`
});
}
};
main.js
도 수정한다.
//main.js의 일부분. create와 udpate에도 동일한 방식으로 수정함.
...
if (queryData.id === undefined) {
const title = "Welcome :)";
const description = "Here is for to test node.js server :)";
const control = `
<input type="button" value="create" onclick="redirect(this, '')"/>
`;
const author = '';
const undefinedCase = new Ready(title, description, control, author);
undefinedCase.makeHtml;
undefinedCase.response(response);
} else {
...
나. readAndRes
readAndRes
함수가 지나치게 복잡한 역할을 수행하고 있었다.
한 번에 한 가지 기능만 수행하는 몇 가지 함수로 나누고 Ready
클래스의 메서드로 만들었다.
template.HTML
, template.List
, template.authorSelect
도 DB를 조회할 수 있도록 했다.
3. db.query 비동기 처리 정상화하기
가. bug 원인 분석
정상적으로 동작하지 않는다.
텅 빈 페이지만 보인다.
아마 Ready.makeHtml
이 완료되지 않고서 Ready.response
가 실행되는 것 같다.
정확하게는 list
를 생성하고 있지 못하고 있다.
template.List
에는 문제가 없음을 확인했다.
List: async function () {
db.query(`SELECT * FROM topic`, (error, topics) => {
if (error) { throw error;}
let list = "<ul>";
topics.forEach(topic => {
list += `<li><a href="/?id=${topic.id}">${topic.title}</a></li>`;
});
list += "</ul>";
return list;
});
},authorSelect:function(author_id){
template.List
의 db.query
가 실행되는 것만 확인하고 template.List
는 동작을 모두 수행했다고 template.HTML
에게 전달하고 실제 db.query
의 콜백함수인 (error, topics)=>{...}
의 동작이 마무리될 때까지 기다리지 않고 template.HTML
이 다음 동작을 수행해서 발생한 문제다.
결론적으로 db.query
의 비동기 작업이 완료될 때까지 기다려줄 필요가 있다.
나. 해결법
MySQL2의 공홈을 살펴보자.
(1) Promise API
MySQL2 also support Promise API. Which works very well with ES7 async await.
(MySQL2는 Promise API도 지원합니다. 이는 ES7 async await와 매우 잘 작동합니다.)
async function main() {
// get the client
const mysql = require('mysql2/promise');
// create the connection
const connection = await mysql.createConnection({host:'localhost', user: 'root', database: 'test'});
// query database
const [rows, fields] = await connection.execute('SELECT * FROM `table` WHERE `name` = ? AND `age` > ?', ['Morty', 14]);
}
MySQL2 use default Promise
object available in scope.
(MySQL2는 사용 가능한 기본 Promise
객체를 사용합니다.) ← JS의 기본 문법에 충실한 사용법인 듯.
.execute
와.query
의 차이점 : prepare statement 사용 유무.
But you can choose which Promise
implementation you want to use.
(하지만 원하는 Promise
구현체를 선택할 수 있습니다.)(Bluebird
는 JavaScript promises 라이브러리입니다.)
// get the client
const mysql = require('mysql2/promise');
// get the promise implementation, we will use bluebird
const bluebird = require('bluebird');
// create the connection, specify bluebird as Promise
const connection = await mysql.createConnection({host:'localhost', user: 'root', database: 'test', Promise: bluebird});
// query database
const [rows, fields] = await connection.execute('SELECT * FROM `table` WHERE `name` = ? AND `age` > ?', ['Morty', 14]);
(2) pool
MySQL2 also exposes a .promise()
function on Pools, so you can create a promise/non-promise connections from the same pool.
(MySQL2는 또한 풀에서 .promise()
함수를 노출시켜 동일한 풀에서 프로미스 또는 논-프로미스 연결을 만들 수 있습니다.)
async function main() {
// get the client
const mysql = require('mysql2');
// create the pool
const pool = mysql.createPool({host:'localhost', user: 'root', database: 'test'});
// now get a Promise wrapped instance of that pool
const promisePool = pool.promise();
// query database using promises
const [rows,fields] = await promisePool.query("SELECT 1");
MySQL2 exposes a .promise() function on Connections, to "upgrade" an existing non-promise connection to use promise.
(MySQL2는 연결(Connections)에 .promise()
함수를 노출시켜 기존의 non-promise 연결을 promise를 사용하도록 "업그레이드"할 수 있습니다.)
// get the client
const mysql = require('mysql2');
// create the connection
const con = mysql.createConnection(
{host:'localhost', user: 'root', database: 'test'}
);
con.promise().query("SELECT 1")
.then( ([rows,fields]) => {
console.log(rows);
})
.catch(console.log)
.then( () => con.end());
다. 적용
공식 홈페이지의 설명을 따라서 db.query
를 처리를 기다리기 위해서 다음과 같이 코드를 수정한다.
//main.js
class Ready{
constructor(title, des, control, author){
this.title = title;
this.description = des;
this.control = control;
this.author = author;
this.html = '';
}
async makeHtml() {
this.html = await template.HTML(this);
}
response(response){
response.writeHead(200);
response.end(this.html);
}
}
async makeHtml() {this.html = await template.HTML(this);}
:template.HTML
이 비동기 함수라서makeHtml
도 비동기 선언
//main.js
...
if (queryData.id === undefined) {
...
const undefinedCase = new Ready(title, description, control, author);
undefinedCase.makeHtml()
.then(()=>{undefinedCase.response(response)});
} else {
undefinedCase.makeHtml().then(()=>{undefinedCase.response(response)});
: Promise chaining. 비동기 함수인.makeHtml
이 완료된 후에.response
실행
//template.js
module.exports ={
HTML: async function (Ready) {
const list = await this.List();
console.log(list);
return `...`;
},List: async function () {
try {
const promiseDB = db.promise();
const [topics, fields] = await promiseDB.execute(`SELECT * FROM topic`);
let list = "<ul>";
topics.forEach(topic => {
list += `<li><a href="/?id=${topic.id}">${topic.title}</a></li>`;
});
list += "</ul>";
return list;
} catch (error) {
throw error;
}
} ...
HTML: async function (Ready) { const list = await this.List();
: 비동기 함수this.List()
를 기다려준다.const promiseDB = db.promise();
:const promiseDB = await mysql.createConnection({...});
와 동일한 효과.
: 그냥db
는 안된다!!!!!!!!!!!!! 허흑..const [topics, fields] = await promiseDB.query(
SELECT * FROM topic);
: destructuring operator 형식으로 작성되었다.
: 실행 결과를 기다려준다.
출처 : https://riverblue.tistory.com/20
출처 : https://surprisecomputer.tistory.com/31
출처 : https://ljtaek2.tistory.com/142
라. 동기, 비동기
자바스크립트는 기본적으로 싱글 스레드를 기반으로 동기 방식으로 작동한다.
- 동기
자바스크립트의 엔진의 주요 구성 요소(Memory Heap, Call Stack) 사용.
Memory Heap : 변수와 객체의 메모리 할당을 담당하는 곳
Call Stack : 함수가 호출이 되면 쌓이는 곳이다. LIFO.
- 비동기
자바스크립트 실행 환경(Runtime)에서는 자바스크립트 엔진 자체가 제공하지 않는 일부 기능
- 비동기를 구현하는 3가지 방법(콜백함수, Promise 객체, async&await)
'공부 중 > Node.js' 카테고리의 다른 글
[Node.js] .env 파일 사용하기 (0) | 2023.07.04 |
---|---|
[Node.js] 모듈로 정리정돈 (0) | 2023.04.01 |
[Node.js] 작성자 표시 (0) | 2023.03.27 |
[Node.js] MySQL로 기능 구현 (Update, Delete) (0) | 2023.03.27 |
[Node.js] MySQL로 기능 구현 (Create, Read) (0) | 2023.03.27 |