FrontEnd/ES&JS 문법

[Node.js] for 동기 처리(Promise, Mysql)

기은P 2022. 10. 21. 17:03
반응형

1. 개요

A 데이터베이스에서 데이터를 가지고 B 데이터베이스에 가공해서 Insert하는 프로그램을 짜는 과정에서 node.js 기반으로 프로그램을 짜보았습니다.

평소에는 자바로 짯지만 node.js로 코드를 짜면 npm 패키지로 쉽게 의존성이 필요한 라이브러리를 다운받을 수 있고, Linux에서도 쉽게 배포할 수 있어서 node.js를 사용해보았습니다.

*필요했던 의존성 라이브러리는 데이터베이스 커넥터와 스케쥴러 2개였습니다.

하지만 java와 다르게 node.js는 대부분 비동기 처리가 되어버려서 데이터를 가공하는데 순서를 맞추기가 굉장히 곤란했습니다.

A function output -> B function execute(x10)

이렇게 순차적으로 실행이 되는 로직으로 코드를 작성했고, java 기반에서는 로직이었는데 Node.jjs에서는 생각하는 방향대로 동작하지가 않았습니다.

 

 

 

2. for 문 안의 Promise

우선 아래 코드를 보면

for (let i = 0; i < 10; i++) {
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(i);
        }, 1000);          // 1초마다 resolve값으로 i가 넘어가서,
    })
    .then((data) => {
    	console.log(data); // 0,1,2.. 이런 순서대로 나오겠지? 
    });
}

1초마다 Promise의 settimeout가 작동해 i가 출력되는 것을 생각을 할텐데 결과는 그렇지가 않고,

1초뒤 0~9가 한꺼번에 출력되는 것을 확인할 수 있습니다.

 

이유는 for문 내부에서 명령문인 new Promise가 실행되어 Promise 객체가 생성되고, 10개의 Promise 객체가 생성되면서 1초뒤에 resolve를 실행하는 명령문이 setTimeout 함수를 통해 예약됩니다.

이때, 동기적인 작업이 for문이 끝나기 전까지는 Promise가 실행되지 않아서

for문이 종료될 때까지 Promise는 pending(대기)에 있다가

for문이 종료되면 1초 뒤 모든 Promise의 resolve를 실행해서 then으로 i값을 전달해서 1초 뒤에 0~9가 한꺼번에 출력되게 됩니다.

참고: https://velog.io/@thms200/2020-01-28-2301-%EC%9E%91%EC%84%B1%EB%90%A8-fwk5xyifw7

 

 

따라서 아래와 같이 Promise 객체를 밖으로 빼낸 상태에서 코드를 작성하면 됩니다.

 async function test() {
  const promiseFunction = (i) =>
        new Promise((resolve) => setTimeout(() => resolve(i), 1000));

    let arr = Array(10).fill(0);
    
    for (let i = 0; i < arr.length; i++) {
        const result = await promiseFunction(i);
        console.log(result);
    }
  }
test()

 

 

3. Mysql Query 동기적 적용

우선적으로 Mysql Query가 동기적으로 적용되는 코드를 보겠습니다.

Node.js에서 사용할 수 있는 Mysql 패키지를 install합니다.

npm i mysql promise-mysql

 

아래는 적용한 전체 코드입니다.

쿼리와 필요한 파라미터에 대한 처리는 자유롭게 하시면 됩니다.

 

index.js

const mysql = require('mysql2/promise');

const func = () => {
  const pool = mysql.createPool({
    host: '주소',
    port: 포트,
    user: '아이디',
    password: '비번',
    database: '스키마'
  });
  
  const getSiteListQuery = async (agoDate, lastDate) => {
    const connection = await pool.getConnection(async conn => conn);
    try {
      const [rows] = await connection.query("SELECT deviceid, siteid  FROM FiveMinuteAccmofMeasureMentsMeter where createts BETWEEN ? and ? GROUP by deviceid, siteid order by siteid, createts desc;", [agoDate, lastDate]);
      connection.release();
      console.log('call')
      return rows
    } catch (err) {
      console.log('Query Error');
      connection.release();
      return false;
    }
  }

  const getSearchList = async (agoDate, lastDate, siteid, deviceid) => {
    const connection = await pool.getConnection(async conn => conn);
    try {
      const [rows] = await connection.query("SELECT * FROM FiveMinuteAccmofMeasureMentsMeter where createts BETWEEN ? and ? and siteid = ? and deviceid = ? order by siteid, createts desc;", [agoDate, lastDate, siteid, deviceid]);
      connection.release();
      return rows
    } catch (err) {
      console.log('Query Error : ' + err);
      connection.release();
      return false;
    }
  }

  let agoDate = new Date(2022, 9, 1);
  let currentDate = new Date(agoDate.getFullYear(), agoDate.getMonth(), 1);
  let currentLastDate = new Date(agoDate.getFullYear(), agoDate.getMonth() + 1, 0);


  getSiteListQuery(currentDate, currentLastDate).then((dataList) => {
    // 주의할 점. foreach를 사용하면 동기적인 처리가 되지 않습니다! 이유는 다음에서...
    // 1. for of 문 사용
    for (const value of dataList) {
      getSearchList(currentDate, currentLastDate, value.siteid, value.deviceid).then(
        data => console.log(data)
      );
    }

    // 2. entries를 사용해 index로 접근 가능
    for (const [index, value] of dataList.entries()) {
      getSearchList(currentDate, currentLastDate, dataList[index].siteid, dataList[index].deviceid).then(
        data => console.log(data)
      );
    }
  })

}
func();

*dataList는 A 데이터베이스에서 받은 데이터리스트고, getSearchList는 Promise 함수로, 파라미터를 받아서 B 데이터베이스에 db query를 실행시키는 함수입니다. 테스트용으로 확인용으로만 썼습니다.

 

주의할 점은 for of또는 array.entries를 사용할 때에 for scope 바깥에서 promise객체를 생성해야하고, foreach를 사용해서 반복하면 안됩니다!

 

 

 

4. foreach를 사용할 때 순차처리가 되지 않는 이유

https://velog.io/@hanameee/%EB%B0%B0%EC%97%B4%EC%97%90-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%EC%8B%A4%EC%8B%9C%ED%95%A0-%EB%95%8C-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%84%EB%B2%95%ED%95%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0%EB%93%A4#for-%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95

 

배열에 비동기 작업을 실시할 때 알아두면 좋은 이야기들

프론트엔드 인턴 면접에서 비동기 작업 관련 질문 대답 못한 뒤 외양간 뚝딱뚝딱 고치는 이야기.

velog.io

추후에 요점 정리해서 다시 작성하겠습니다.

 

반응형