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

문제 상황

회사 프로젝트에서 Node.js 6에서 8 LTS로 마이그레이션을 진행하면서, 콜백 지옥으로 작성된 파일 처리 로직을 개선할 기회가 생겼다. 기존에는 bluebird 같은 외부 라이브러리를 사용했지만, Node.js 8부터 util.promisify가 표준으로 제공된다.

기존 콜백 코드

const fs = require('fs');

fs.readFile('config.json', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  const config = JSON.parse(data);
  fs.writeFile('output.json', JSON.stringify(config), (err) => {
    if (err) console.error(err);
  });
});

util.promisify 적용

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

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);
    await writeFile('output.json', JSON.stringify(config));
  } catch (err) {
    console.error(err);
  }
}

주의사항

util.promisify는 Node.js 콜백 컨벤션 (err, value) => {}을 따르는 함수에서만 작동한다. 커스텀 콜백 패턴을 사용하는 경우 util.promisify.custom 심볼을 활용해야 한다.

function customFunc(callback) {
  // 커스텀 로직
}

customFunc[util.promisify.custom] = () => {
  return new Promise((resolve, reject) => {
    // Promise 래핑
  });
};

결과

외부 의존성 없이 비동기 코드를 깔끔하게 작성할 수 있게 되었다. async/await와 조합하니 가독성이 크게 개선되었고, 에러 핸들링도 try-catch로 통일할 수 있었다. Node.js 8 LTS 도입을 고려 중이라면 util.promisify는 충분히 활용할 가치가 있다.