Skip to content

타임라인 격리하기

이번 글에서는 시간에 따라 실행되는 액션의 순서를 나타내기 위해 타임라인 다이어그램에 대해서 알아보겠습니다.

스터디 회차: 14회차, 15회차 (2025년 9월 10일, 11일)

타임라인 다이어그램

타임라인 다이어그램이란

  • 타임라인은 액션을 순서대로 나열한 것입니다.
  • 타인라인 다이어그램은 시간에 따른 액션 순서를 시각적으로 표시한 것입니다.

규칙

  1. 두 액션이 순서대로 나타나면 같은 타임라인에 넣습니다.
  2. 두 액션이 동시에 실행되거나 순서를 에상할 수 없다면 분리된 타임라인에 넣습니다.

타임라인 그리기

타임라인 다이어그램을 그리는 과정

  1. 액션을 확인합니다.
  2. 각 액션을 그립니다.
  3. 단순화합니다.

예제: 사용자와 문서를 저장하는 로직

tsx
saveUserAjax(user, function () {
  setUserLoadingDOM(false);
});
setUserLoadingDOM(true);
saveDocumentAjax(document, function () {
  setDocLoadingDOM(false);
});
setDocLoadingDOM(true);

1. 액션을 확인합니다

tsx
saveUserAjax();
setUserLoadingDOM(false);
setUserLoadingDOM(true);
saveDocumentAjax();
setDocLoadingDOM(false);
setDocLoadingDOM(true);

2. 각 액션을 그립니다

타임라인 격리하기

  • 자바 스크립트 코드는 위에서 아래로 실행됩니다.
  • 아직 타임라인이 없기 때문에 새로운 타임라인을 만듭니다.

타임라인 격리하기

  • 콜백안에 있는 코드입니다.
  • 콜백은 비동기로 실행되기 때문에 요청이 끝나는 시점에 언젠가 실행됩니다.
  • 비동기 콜백이기 때문에 새로운 타임라인이 필요합니다.

타임라인 격리하기

  • 다음 실행되는 코드는 콜백이 아니므로 원래 타임라인에서 실행됩니다.

타임라인 격리하기

  • 두번째 콜백함수의 경우는 새로운 타임라인에 추가하여 타임라인 다이어그램을 완성합니다.

3. 단순화합니다

  1. 하나의 타임라인에 있는 모든 액션을 하나로 통합합니다.
  2. 타임라인이 끝나는 곳에서 새로운 타임라인이 하나 생긴다면 통합합니다.

타임라인 격리하기

  1. 자바스크립트는 하나의 스레드이기 때문에 액션은 순서가 섞이지 않고 하나의 타임라인에서 실행됩니다. 따라서 하나로 통합할 수 있습니다.
  2. 타임라인이 끝나는 곳에 새로운 타임라인이 두개 생기기 때문에 통합하지 않습니다.

순서대로 실행되는 코드도 두 가지 종류가 있습니다

타임라인 격리하기

  • 두 액션 사이에 시간이 얼마나 걸릴지 알 수 없는 코드는 순서가 섞일 수 있는 코드입니다.
  • 두 액션이 차례로 실행되는데 그 사이에 다른 작업이 끼어들 수 없는 코드는 순서가 섞이지 않는 코드입니다.
    • 이런 경우는 액션을 같은 상자에 그립니다.

동시에 실행되는 코드는 실행순서를 확신할 수 없습니다

타임라인 격리하기

  • 동시에 실행되는 코드는 타임라인 다이어그램에 나란히 표현합니다.
  • 하지만 항상 정확히 동시에 실행된다는 의미는 아닙니다.
  • 따라서 다르게 그렸지만 모두 같은 것을 의미합니다.

예제2: 아이템을 장바구니에 추가하기

tsx
function add_item_to_cart(name, price, quantity) {
  cart = add_item(cart, name, price, quantity);
  calc_cart_total();
}

function calc_cart_total() {
  total = 0;
  cost_ajax(cart, function (cost) {
    total += cost;
    shipping_ajax(cart, function (shipping) {
      total += shipping;
      update_total_dom(total);
    });
  });
}

1. 액션 확인하기

tsx
1. cart 읽기
2. cart 쓰기
3. total = 0 쓰기
4. cart 읽기
5. cost_ajax() 부르기
6. total 읽기
7. total 쓰기
8. cart 읽기
9. shipping_ajax() 부르기
10. total 읽기
11. total 쓰기
12. total 읽기
13. update_total_dom() 부르기

+= 는 사실 세단계입니다

tsx
// 코드
total += cost;

// 실제 연산
var temp = total; // total 읽기
temp = temp + cost; // 계산
total = temp; // total 쓰기
  • 모두 세단계로 이루어져 있습니다.
  • total 값이 전역변수라면 첫번째 단계와 세번째 단계는 액션입니다.
  • 따라서 두 개의 액션으로 다이어그램에 표시해야 합니다.

2. 다이어그램 그리기

타임라인 격리하기

  1. 액션이 순서대로 실행되면 같은 타임라인에 그립니다.
  2. 액션이 동시에 실행되면 새로운 타임라인에 그립니다.

3. 단순화하기

타임라인 격리하기

  • 자바스크립트는 하나의 스레드에서 실행되기 때문에 모든 액션을 박스 하나에 넣을 수 있습니다.
  • 비동기 액션 이후에 연속되는 콜백이 하나라면 하나의 타임라인으로 통합할 수 있습니다.

모든 액션을 하나의 박스로 넣을수 있을까요?

  • 다시 단순화하기 첫번째 단계를 적용해서 모든 액션을 하나의 박스로 넣을 수 있을까요?
  • 그럴 수 없습니다.
  • 만약 다른 타임라인에 액션이 생긴다면 각 박스는 순서가 섞일 가능성이 있기 때문입니다.

자원을 공유하는 타임라인은 문제가 생길 수 있습니다

예제: 장바구니에 아이템 담는 것을 빠르게 두 번 실행

타임라인 격리하기

  • 빠르게 두번 실행해서 순서가 섞이게 되면 기대와 다른 연산 결과가 나올 수 있습니다.
  • 문제는 공유하는 자원 때문에 발생합니다.

전역 변수를 지역 변수로 바꾸기

타임라인 격리하기

  • total 값이 0이 아닐수 있습니다. 전역 변수이기 때문에 다른 타임라인에서 값을 바꿀 수 있습니다.
  • 지역 변수로 바꾼 다음에는 함수 밖으로 영향을 주지 않기 때문에 더이상 액션이 아닙니다.
  • 따라서 타임라인에서 제거할 수 있습니다.

전역 변수를 인자로 바꾸기

타임라인 격리하기

  • calc_cart_total 안에서 사용하는 두개의 cart는 전역 벼수를 참조하고 있습니다. 두개의 코드 사이에 cart가 변경된다면 예상치 못한 결과가 나올 수 있습니다.
  • 인자로 변경하여 같은 cart에 대해서 동작하도록 수정할 수 있습니다.
  • 따라서 cart는 서로 다른 타임라인에 영향을 주지 않습니다.

함수 본문을 콜백으로 바꾸기

tsx
function add_item_to_cart(name, price, quant) {
  cart = add_item(cart, name, price, quant);
  calc_cart_total(cart);
}

function calc_cart_total(cart) {
  var total = 0;
  cost_ajax(cart, function (cost) {
    total += cost;
    shipping_ajax(cart, function (shipping) {
      total += shipping;
      update_total_dom(total);
    });
  });
}
  • 마지막으로, 계산된 결과를 반드시 update_total_dom 을 실행하지 않고 다른 동작을 수행할 수 있으므로 이 부분을 개선할 수 있습니다.
  • total 값을 리턴하는 방법은, 비동기 호출이 끝나야 결과가 나오기 때문에 리턴하는 방법은 사용할 수 없습니다.
  • 따라서 함수 본문을 콜백으로 바꾸기 리팩토링을 통해 비동기 호출의 결과로 실행할 함수를 전달받아 개선할 수 있습니다.
tsx
function add_item_to_cart(name, price, quant) {
  cart = add_item(cart, name, price, quant);
  calc_cart_total(cart, update_total_dom);
}

function calc_cart_total(cart, callback) {
  var total = 0;
  cost_ajax(cart, function (cost) {
    total += cost;
    shipping_ajax(cart, function (shipping) {
      total += shipping;
      callback(total);
    });
  });
}
  • 이제 total 값을 다른 계산에서도 사용할 수 있습니다.

async/await을 사용해서 개선할 수 있지 않나요?

tsx
async function calc_cart_total(cart, callback) {
  var total = 0;

  const cost = await cost_ajax(cart);
  total += cost;

  const shipping = await shipping_ajax(cart);
  total += shipping;

  return total;
}
  • 책의 내용과 별개로 최근에는 async/await을 사용해 비동기 코드를 동기 코드처럼 작성할 수 있습니다.
  • 이는 코드 가독성을 높이고 흐름을 직관적으로 만듭니다.
tsx
async function add_item_to_cart(name, price, quant) {
  cart = add_item(cart, name, price, quant);
  const total = await calc_cart_total(cart); // total이 계산될때까지 기다림
  update_total_dom(total);

  // total, dom 업데이트와 상관없는 로직
}
  • 하지만 모든 코드를 기다리게 하므로 필요 없는 경우 병목 현상을 일으킬 수 있습니다.
  • 따라서 상황에 따라 callback 방식과 async/await 방식을 적절히 선택하는 것이 중요합니다.

정리하기

  • 타임라인 다이어그램은 비동기 코드의 실행 순서를 시각적으로 표현하는 도구입니다.
  • 타임라인 다이어그램의 기본 규칙
    • 순차적으로 실행되는 코드는 같은 타임라인에 넣는다.
    • 동시에 실행되거나 실행 순서를 확신할 수 없는 코드는 별도의 타임라인으로 분리한다.
  • 타임라인 다이어그램을 그리는 과정
    1. 액션을 확인합니다.
    2. 각 액션을 그립니다.
    3. 단순화합니다.
  • 공유 자원 문제
    • 여러 타임라인에서 같은 전역 변수를 다루면 값이 꼬이거나 예상치 못한 결과가 발생한다.
    • 이를 해결하기 위해서는
      • 전역 변수를 지역 변수로 바꾸거나,
      • 전역 변수를 함수 인자로 전달하거나,
      • 함수 본문을 콜백으로 바꿔서 안전하게 실행 흐름을 제어하는 방법을 사용할 수 있다.