Node.js 8 LTS에서 async/await를 도입하며 겪은 점들

배경

10월에 Node.js 8이 LTS로 전환되면서 프로덕션 환경에 안정적으로 적용할 수 있게 되었다. 가장 큰 변화는 async/await를 네이티브로 사용할 수 있다는 점이었다. 기존 Promise 체이닝 코드를 리팩토링하면서 몇 가지 배운 점을 정리한다.

Promise 체이닝에서 async/await로

기존에는 이런 식으로 작성했다.

function getUserData(userId) {
  return db.findUser(userId)
    .then(user => {
      return db.findPosts(user.id);
    })
    .then(posts => {
      return posts.map(formatPost);
    })
    .catch(err => {
      logger.error(err);
      throw err;
    });
}

async/await로 바꾸니 훨씬 읽기 쉬워졌다.

async function getUserData(userId) {
  try {
    const user = await db.findUser(userId);
    const posts = await db.findPosts(user.id);
    return posts.map(formatPost);
  } catch (err) {
    logger.error(err);
    throw err;
  }
}

실수했던 부분

초반에 병렬로 실행할 수 있는 부분을 순차적으로 처리해서 성능 저하가 있었다.

// 잘못된 예 - 순차 실행
const user = await db.findUser(userId);
const settings = await db.findSettings(userId);

// 개선 - 병렬 실행
const [user, settings] = await Promise.all([
  db.findUser(userId),
  db.findSettings(userId)
]);

Express 미들웨어에서의 에러 처리

Express는 아직 async 함수의 에러를 자동으로 캐치하지 못한다. 래퍼 함수를 만들어 해결했다.

const asyncHandler = fn => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await db.findUser(req.params.id);
  res.json(user);
}));

정리

async/await 도입으로 콜백 지옥과 Promise 체이닝에서 벗어났다. 하지만 병렬 처리가 필요한 부분은 여전히 Promise.all을 사용해야 하고, 에러 핸들링도 명시적으로 처리해야 한다는 점을 잊지 말아야겠다.