[Node.js] 비동기 처리 (util 모듈사용)

예리에르·2021년 8월 12일
3

Backend

목록 보기
1/3
post-thumbnail

node.js와 redis를 사용해서 backend를 구성하였다. map에 hget을 활용하여 데이터를 조회하는 작업을 하던중 비동기로 인해 문제가 발생했다.

문제의 시작

function getTestDetail(testList) {
    let testDetailList = new Array();
    Promise.all(testList.map(async (testID) => {
        let data = await client.hgetall(`test_${testID}`, (err, test) => {
            console.log(test)
            return test
        });
        console.log('datat',data)

    }));

    return testDetailList
};

...
app.get("/:id", async (req, res, next) => {
    try {
        const testList = [1, 2, 5];
        const testDetailList = await getTestDetail(testList);
        // console.log(testDetailList,4444)
        res.json(testDetailList)
    } catch (e) {
        return next(e)
    }
})

비동기 처리의 필요성

'/:id path로 요청이 들어오면 id에 해당하는 유저의 문제번호를 확인하고 번호에 맞는 데이터를 보내줘야한다. 여기서 문제는 javascript가 map을 통해 데이터를 조회하는 과정을 기다려주지 않는다는 점이었다.

그래서 데이터를 console.log() 찍게되면 undefine,null,[] 과 같은 결과만 나오게 되었다.

async/await 사용

  • 기존에 작업했던 코드를 참고해서 async/await를 적용하기 위해 공부하였다. 하지만 프론트에서 요청을 보내기 위한 비동기 처리는 백에서 데이터를 조회할 때 필요한 비동기 처리와 이질감이 느껴졌다.
  • async/await를 원하는 방향대로 사용하기 위해서는 더 근본적인 promise 객체에 대한 공부가 중요하다고 생각하고 promise를 키워드로 공부를 진행했다.

util 모듈의 promisify

  • Node 8.0.0 부터 추가된 함수.
  • 콜백 함수를 사용하여 응답을 반환하는 메서드를 변환하여 Promise 객체에 응답으로 반환하는데 사용된다.

사용방법

const { promisify } = require("util");

먼저 util 모듈에서 promisify를 불러옵니다.

const client = redis.createClient()
...
const getallAsync = promisify(client.hgetall).bind(client);
const getAsync = promisify(client.hget).bind(client);
const hgetAsync = promisify(client.get).bind(client);
const asmembers = promisify(client.smembers).bind(client)
const ahkeys = promisify(client.hkeys).bind(client)
  • redis 객체를 만든다. 이후 비동기로 작동하기 원하는 메소드의 실행 컨텍스트를 유지시켜주기 위해 바인드를 해준다.

호출 방법

1. Promise 호출

return getAsync('foo').then(function(res) {
  console.log(res);
}));

2. async/await 호출

async myFunc() {
  const res = await getAsync('foo');
  console.log(res);
}

대표 2가지의 비동기 호출방법이 존재한다. 나는 여기서 async/await를 활용해서 원하는 데이터를 조회하고 가공해서 response 하였다.

Promise.all()

  • Priomise.all()은 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환합니다. 주어진 프로미스 중 하나가 거부하는 경우, 첫 번째로 거절한 프로미스의 이유를 사용해 자신도 거부합니다.
    (https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)
  • Promise.all()을 사용해서 비동기처리를 할 수있다. 여기서 aync/await와 차이점은 병렬로 처리한다는 점이다.


여기서 봐야하는 점은 "순서대로 실행되지만 앞의 함수가 완료되는 것을 기다리지 않는다"는 점이다. 마지막으로 완료되는 함수까지 기다린 후 값을 반환한다.반환순서는 실행 순서와 동일하게 준다.
즉, Promise.all()은 비동기 작업을 병렬로 실행하고 결과를 배열로 빠르게 확인할 수 있는 함수라고 생각하면 된다.

코드 정리

app.get("/:id", async (req, res, next) => {
    const id = req.params.id;
    const getTest = await getallAsync(`users_${id}`)
    const {test = ""} = getTest || {};
    const testIDs = test.split(',').map(ele => parseInt(ele));
    const testList = [];
    await Promise.all(testIDs.map(async (testID) => {
        const data = await getallAsync(`test_${testID}`)
        testList.push(data)
    }))
    return res.status(200).json({data: testList});
});
  • 그래서 위의 내용을 바탕으로 코드를 설명하면 2개의 hgetall을 통해 데이터를 조회하고 promise.all()을 통해 얻은 배열을 순회한다. 이렇게 비동기 처리를 통해 원하는 데이터를 순회하고 값을 얻을 수 있게 된다.

+) 보통 비동기처리를 하게되면 try catch로 에러처리를 하게된다. 하지만 그렇게 되면 중간에 에러가 발생할 때 디버깅할 때 어려움이 발생하게 된다. 그래서 개발단계에서는 최대한 코드에서 보안코드 작성으로 에러를 처리하고 QA까지 했는데도 찝찝하게 에러가 걱정된다면 try catch를 도입해 에러를 처리하자!

참고)
https://flaviocopes.com/node-promisify/

https://ivorycirrus.github.io/TIL/node-redis-async/

https://code-masterjung.tistory.com/91

profile
비전공 프론트엔드 개발자의 개발일기😈 ✍️

0개의 댓글