Blocking

Table Of Contents

  1. 블로킹
  2. 콜백 함수
  3. Arrow Function
  4. Welcome To Callback Hell
  5. Promise
  6. 비동기 제어 디자인 패턴 GET
  7. 비동기 제어 디자인 패턴 POST
  8. 비동기 제어 디자인 패턴 PUT
  9. 비동기 제어 디자인 패턴 DELETE

블로킹

  • Node.js 프로세스에서 추가적인 JavaScript의 실행을 위해 JavaScript가 아닌 작업이 완료될 때까지 기다려야만 하는 상황
  • 일반적으로 NodeJS의 I/O 논블로킹으로 앞선 작업이 완료될때까지 후순위 작업이 기다려주지 않습니다.
  • I/O란 인풋,아웃풋으로 일반적으로 시스템 디스크 및 네트워크와 상호작용하는 것으로 이해하면 됩니다.
// 블로킹 코드
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 파일을 읽을 때까지 여기서 블로킹 됩니다.
//위 코드는 에러 핸들링이 없음

//non 블로킹 코드
//논블로킹 코드는 기다려 주지 않음으로, 작업이 완료된 후 코드 실행을 원한다면 아래와 같이 콜백 함수를 만들어야 합니다.
fs.readFile('/file.md', (err, data) => {
    if (err) throw err;
    console.log(data);
});

논블로킹을 사용하는 이유는 웹앱의 퍼포먼스를 극대화 하기 위함입니다.
데이터를 불러올 때마다 지연이 발생하고, 다른 작업이 완료되지 못한다면 앱의 성능이 확연히 떨어질 것입니다.

blocking_structure

⬆ back to top

콜백 함수

  • 첫번째 함수가 실행된 결과에 따라 실행되는 함수이다.
  • 위의 readFile 함수의 파일을 읽은 결과를 알고 싶을 경우, 애로우 펑션을 이용해 콜백함수를 구현하여 확인할 수 있었다.
function callFunction(callback){
  console.log("callFunction called")
  let one = 1
  let two = 2
  let result = one+two
  if(result>2){
      callback("biggner than 2")
  } else {
      callback("not biggner than 2")
  }
}

callFunction(function(message){
    console.log(message)
})

Arrow Function

  • 화살표 함수는 익명함로 function이라는 표현을 대신하는 함수 입니다.
  • 다만 this, super 등이 없고 생성자로 사용할 수 없습니다.
  • 이 때문에 메소드로는 사용할 수 없고, 주로 콜백함수로 많이 사용합니다.
var wakeUp =  function(){
  console.log('wake up!')
}
setInterval(wakeUp, 1000);
//특정 기간동안 특정 기능을 실행 하는 함수
//wakeUp은 setInterval이라는 함수의 결과로 실행되는 'callback' 함수 입니다.

//위 함수는 아래와 같이 익명함수로 변경할 수 있습니다.
setInterval(function(){
  console.log('wake up!')
}, 1000);

//그리고 이러한 익명함수는 아래와 같이 화살표 함수 표현으로 대체할 수 있습니다.
setInterval(() => {
  console.log('wake up!')
}, 1000);

⬆ back to top

Welcome To Callback Hell

  • 예전 Javascript 개발자는 콜백 지옥이라는 표현을 많이 썼습니다.
  • 우리가 다음과 같은 함수를 순차적으로 작성한다고 해봅시다.
setTimeout(() => {
    console.log('sleep')
}, 3000);
setTimeout(() => {
    console.log('wake up!')
}, 1000);
console.log('eat');

저는 3초를 자고, 1초 후에 일어나서, 먹기를 바라지만 실제 실행되는 코드는 그렇지 않을 것입니다.
때문에 우리는 다음과 같이 코드를 짜게 됩니다.

setTimeout(() => {
  console.log('sleep')
    setTimeout(() => {
      console.log('wake up!')
      console.log('eat');
  }, 1000);    
}, 3000);

선생님👨‍🏫: 벌써 좀 복잡해 보이는게 느껴지시나요?
만약 이 콜백이 여러번 가게 된다면? 그것이 바로 콜백 지옥 입니다.

Promise

  • 이런 논블로킹 코드를 제어하기 위해 우리는 Promise를 씁니다.
  • Promise를 어떻게 쓰는 지 알아보기 위해 다음과 같은 간단한 코드를 한번 작성해 봅니다.
  • 우리는 Router가 너무 복잡해져서, query 모델을 분리하기로 결정했습니다.
module.exports.getList = (connection, options) => {
  connection.query('SELECT * FROM schools ', options.schoolName,  function (error, results, fields) {
    if (error) {
      res.status(404).json({result:false})
        throw error;
    }                   
    connection.commit(async function(err) {
      if (err) {
        res.status(404).json({result:false})
        throw err;                    
      }                
      console.log('results : ',results)
      return results;
    });                
  });    
});

그리고 기존 코드에서는 아래와 같이 getList 모듈을 가져오기로 합니다.

var school = require('../models/school')
pool.getConnection(function(err, connection) {
  const results =  school.getList(connection, {schoolName:schoolName})
  console.log('success')
  res.status(200).json({result:results})        
})

선생님👨‍🏫: 위 코드를 실행해보면, 쿼리 문의 results는 콘솔로 불러올 수 있지만 어쩐일인지, 클라이언트에 응답하는 result는 빈 값으로 나가게 됩니다.
NodeJS의 논블로킹에 따라, 디비에서 데이터를 가져오기 전에 이미 응답하기 때문입니다.
우리는 위 코드를 아래와 같이 바꿀 수 있습니다.

module.exports.getList = (connection, options) => {
  return new Promise((resolve, reject) => {
    connection.query('SELECT * FROM schools ', options.schoolName,  function (error, results, fields) {
      // connection.query(query,  function (error, results, fields) {
          if (error) {
            res.status(404).json({result:false})
              throw error;
          }                   
          connection.commit(async function(err) {
            if (err) {
              res.status(404).json({result:false})
              throw err;                    
            }                
            console.log('results : ',results)
            resolve(results);
          });                
      });
  });    
};
var school = require('../models/school')
pool.getConnection(async function(err, connection) {
  const results = await school.getList(connection, {schoolName:schoolName})
  console.log('success')
  res.status(200).json({result:results})        
})

선생님👨‍🏫: 그리고 다시 실행해 보면 이번에는 무사히 값들을 불러오는 걸 알 수 있습니다!

  • resolve : 결정 (return)
  • reject : 거부 (에러)
  • async : 함수를 블로킹으로 제어하기 위해 붙여주는 명령어
  • await : 기다려야 하는 함수 앞에 추가

선생님👨‍🏫: 여기서 하나 알아야할 것이 있습니다.
우리는 구조화하기 위해서 Promise를 사용한 것이 아닙니다.
우리는 구조화하는 과정에서 비동기 흐름을 제어할 필요를 느꼈고, 그렇기 때문에 Promise를 사용한 것입니다.
일종의 디자인 패턴을 만들기 위해서 꼭 Promise를 써야 하는 것은 아니라는 점을 말씀드리고 싶었습니다.

⬆ back to top

비동기 제어 디자인 패턴 GET

  • 그리고 이렇게 Promise를 활용하면 코드를 더욱 멋지게 구조화할 수 있습니다.
  • 먼저 components에 db.js 파일을 만들고, 아래와 같이 코드를 넣어 줍니다.
const config = require('../config')
const mysql = require('mysql')

var pool  = mysql.createPool({
  connectionLimit : config.database.connectionLimit,
  host: config.database.host,
  user: config.database.user,
  password: config.database.password,
  database: config.database.database
});

module.exports.query = (options) => {
  return new Promise((resolve, reject) => {
    pool.getConnection(function(err, connection) {
      if (err) reject(err); // not connected!
      connection.query(options.query, options.value,  function (error, results, fields) {
        if (error) {                    
          reject(error)
        }                   
        connection.commit(async function(err) {
          if (err) {
            reject(err)                 
          }                
          console.log('results : ',results)
          resolve(results);
        });                
      });
    });
  });    
}

선생님👨‍🏫:
위 query 문 안에는 getConnection과 query 명령어를 돌리는 코드가 모두 들어가 있습니다.
즉 이제 반복적으로 해당 문을 다시 쓰지 않아도 됩니다.
그리고 models라는 폴더를 만들어 school.js를 생성하겠습니다.
이제 실제 query에 해당하는 명영어는 이 model 디렉토리에서 작성할 예정입니다.

const db = require('../components/db')

module.exports.getList = async (options) => {
  console.log('options : ',options)
  return await db.query({
    query: 'SELECT * FROM schools WHERE name = ?',
    value: options.schoolName
  })        
};

선생님👨‍🏫: 위와 같이 작성하면 이제 간단하게 query를 관리할 수 있습니다!
마지막으로 routes의 school.js 의 get 메소드는 다음과 같이 간단하게 바꿔줄 수 있습니다.

router.get('/', async function (req, res) {
  const schoolName = req.query.schoolName
  const results = await school.getList({schoolName:schoolName})
  console.log('success')
  res.status(200).json({result:results})            
})

비동기 제어 디자인 패턴 POST

  • 그리고 post도 상당히 다르게 변경할 수가 있습니다.
  • POST, UPDATE, DELETE의 경우 우리는 rollback을 사용하기 때문에, beginTransaction으로 코드를 시작해야 합니다.
  • 그래서 db.js에 아래 코드를 추가하겠습니다.
module.exports.beginTransaction = () => {
  return new Promise((resolve, reject) => {
    pool.getConnection(function(err, connection) {
      if (err) reject(err); // not connected!
        // Use the connection
      connection.beginTransaction(function(err) {
        if (err) { reject(err); }
        resolve(connection)
      });
    });
  })
}

선생님👨‍🏫: 또한 commit과 rollback도 구현 해 줍니다.

module.exports.commit = connection => {
    return new Promise((resolve, reject) => {
        connection.commit(err => {
            if (err) reject(this.rollback(connection));
            else {
                connection.release();
                resolve();
            }
        });
    });
};

module.exports.rollback = connection => {
    return new Promise((resolve, reject) => {
        connection.rollback(err => {
            if (err) reject(err);
            else {
                connection.release();
                resolve();
            }
        });
    });
};

routes/school.js의 post 메소드를 수정해 보겠습니다.

router.post('/', async function (req, res) {
    try {
        const json = req.body;
        const connection = await db.beginTransaction();
        var newSchool = { name: json.name, address: json.address, type: json.type };
        const results = await school.insert(connection, newSchool);
        await db.commit(connection);
        if (results) {
            res.status(200).json({ result: results.insertId });
        }
    } catch (err) {
        console.log('err : ', err);
        await db.rollback(connection);
        next(err);
    }
});

선생님👨‍🏫:

  • try...catch에서 catch는 throw err가 일어났을 시(에러가 발생했을 시에), 실행하는 코드 입니다.
  • 이때 우리는 rollback을 실행해 주며 next 함수를 실행합니다.
  • next는 다음 미들웨어를 실행시키는 함수로, 에러가 발생했을 시 다음 미들웨어를 실행하도록 하여 넘어갑니다.
  • 그리고 insert 코드는 다음과 같이 아름답게 변합니다.
module.exports.insert = async (connection, options) => {
    console.log('options : ', options);
    return await db.query({
        connection: connection,
        query: 'INSERT INTO schools SET ?',
        value: options,
    });
};

비동기 제어 디자인 패턴 PUT

선생님👨‍🏫: put은 이제 아주 손쉽게 고칠 수가 있습니다.

// routes/school.js
router.put('/', async function (req, res) {
    try {
        const json = req.body;
        const connection = await db.beginTransaction();
        var newSchool = { name: json.name, address: json.address, type: json.type };
        const results = await school.update(connection, {
            options: newSchool,
            idx: json.idx,
        });
        await db.commit(connection);
        if (results) {
            res.status(200).json({ result: results });
        }
    } catch (err) {
        console.log('err : ', err);
        await db.rollback(connection);
        next(err);
    }
});

//models/shool.js
module.exports.update = async (connection, options) => {
    console.log('options : ', options);
    return await db.query({
        connection: connection,
        query: 'UPDATE schools SET ? WHERE idx = ?',
        value: [options.options, options.idx],
    });
};

비동기 제어 디자인 패턴 DELETE

선생님👨‍🏫: delete 역시 마찬가지 입니다.

// routes/school.js
router.delete('/', async function (req, res) {
    const json = req.body;
    try {
        const json = req.body;
        const connection = await db.beginTransaction();
        const results = await school.delete(connection, json.idx);
        await db.commit(connection);
        if (results) {
            res.status(200).json({ result: results });
        }
    } catch (err) {
        console.log('err : ', err);
        await db.rollback(connection);
        next(err);
    }
});

//models/shool.js
module.exports.delete = async (connection, options) => {
    console.log('options : ', options);
    return await db.query({
        connection: connection,
        query: 'DELETE FROM schools WHERE idx = ?',
        value: [options],
    });
};

⬆ back to top

results matching ""

    No results matching ""