20170117

Async/Await on Node 7 — production memo

Node 7.x에서 --harmony 없이 async/await 쓸 수 있게 됨. 6.10 LTS에선 아직 flag 필요하긴 한데, 7.6부터는 기본 활성화. 프로덕션 API 하나 이걸로 작성하면서 느낀 것들.

promise chain 지옥을 벗어나니까 확실히 코드가 읽힘:

// before (promise chain)
function getOrder(id) {
  return db.Order.findById(id)
    .then(order => {
      return db.User.findById(order.userId)
        .then(user => ({ order, user }));
    })
    .then(({ order, user }) => {
      return api.tracking(order.shipmentId)
        .then(tracking => ({ order, user, tracking }));
    });
}

// after
async function getOrder(id) {
  const order = await db.Order.findById(id);
  const user = await db.User.findById(order.userId);
  const tracking = await api.tracking(order.shipmentId);
  return { order, user, tracking };
}

운영에서 배운 것들:

1. error handling은 try/catch. 안 감싸면 unhandledRejection 뜨고 메모리 새기 시작. express 핸들러는 래퍼 하나 만들어서 씀:

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

app.get('/orders/:id', asyncHandler(async (req, res) => {
  const result = await getOrder(req.params.id);
  res.json(result);
}));

2. 병렬화 놓치기 쉬움. 위 예시처럼 await를 줄줄이 쓰면 순차 실행이라 느림. 병렬 가능한 건 Promise.all로 묶어야 함:

const [user, tracking] = await Promise.all([
  db.User.findById(order.userId),
  api.tracking(order.shipmentId)
]);

3. 스택 트레이스. async 함수는 예전 callback/promise보다 스택이 잘 남음. 7.x에서 꽤 개선된 편. 그래도 async_hooks 같은 도구는 아직 실험적.

4. V8 버전. Node 7은 단명 (LTS 아님). 6 LTS 쓰려면 --harmony-async-await. 8이 4월 릴리즈 예정이고 곧 LTS 전환될테니 정식 production 이관은 8 나온 다음이 안전.

코드 리뷰 때 await 붙이고 Promise.all로 묶는 타이밍 훈련만 되면 생산성 상승이 체감됨. callback hell 시절에서 정말 많이 왔다.