Node.js 8의 util.promisify로 콜백 지옥 탈출하기

문제 상황

프로젝트에서 파일 시스템 작업과 외부 API 호출이 중첩되면서 콜백 지옥이 심각해졌다. Promise를 직접 래핑하는 방식으로 해결하고 있었지만, 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 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);
    await writeFile('output.json', JSON.stringify(config));
  } catch (err) {
    console.error(err);
  }
}

processConfig();

커스텀 함수 promisify

레거시 API 중에는 에러-퍼스트 콜백 패턴을 따르지 않는 경우도 있었다. 이런 경우 util.promisify.custom 심볼을 사용해 커스텀 promisify 로직을 구현할 수 있다.

function customAPI(arg, callback) {
  // 비표준 콜백 패턴
  setTimeout(() => callback(arg * 2), 100);
}

customAPI[util.promisify.custom] = (arg) => {
  return new Promise((resolve) => {
    customAPI(arg, resolve);
  });
};

const promisifiedAPI = util.promisify(customAPI);

결과

프로젝트 전반에 걸쳐 콜백 기반 코드를 async/await으로 전환했다. 코드 가독성이 크게 개선되었고, 에러 핸들링도 try-catch로 통일할 수 있었다. Node.js 8 업그레이드의 가장 큰 수확이었다.