Node.js 8 LTS에서 util.promisify로 콜백 지옥 탈출하기

문제 상황

레거시 프로젝트에서 파일 시스템 작업과 DB 쿼리가 중첩되면서 콜백이 5단계까지 깊어지는 코드가 있었다. 에러 핸들링도 각 단계마다 중복되어 유지보수가 어려웠다.

fs.readFile('config.json', (err, data) => {
  if (err) return callback(err);
  db.query('SELECT * FROM users', (err, users) => {
    if (err) return callback(err);
    // 계속 중첩...
  });
});

util.promisify 도입

Node.js 8.0.0부터 util.promisify가 추가되어 표준 콜백 패턴을 Promise로 쉽게 변환할 수 있게 되었다. 기존 bluebird 같은 외부 라이브러리 없이 네이티브로 해결 가능해졌다.

const util = require('util');
const fs = require('fs');

const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);

async function processConfig() {
  try {
    const data = await readFile('config.json', 'utf8');
    const config = JSON.parse(data);
    const users = await db.query('SELECT * FROM users');
    const result = await processUsers(users, config);
    await writeFile('output.json', JSON.stringify(result));
    return result;
  } catch (err) {
    console.error('처리 실패:', err);
    throw err;
  }
}

적용 결과

  • 코드 가독성이 크게 개선되었다
  • 에러 핸들링을 try-catch 하나로 통일했다
  • 중첩 depth가 5단계에서 1단계로 줄었다
  • 테스트 코드 작성도 훨씬 쉬워졌다

다만 기존 콜백 API가 (err, result) 패턴을 따르지 않는 경우는 수동으로 Promise 래퍼를 만들어야 했다. 전체 프로젝트에 일괄 적용하기보다는 복잡도가 높은 모듈부터 점진적으로 리팩토링하는 방식으로 진행했다.