Skip to content

타임라인 사이에 자원 공유하기

지난 글에서는 타임라인끼리 공유 자원을 줄이는 방법에 대해서 알아보았습니다.
이번 글에서는 자원공유가 필요한 경우 안전하게 공유하는 방법에 대해서 알아봅니다.

스터디 회차: 16회차 (2025년 9월 16일)

실행 순서 보장

타임라인 사이에 자원 공유하기

문제점

  • 여전히 DOM 자원을 공유하기 때문에 문제점이 발생합니다.
  • 업데이트 순서를 제한해야합니다.

큐를 이용해서 개선하기

  • 큐는 들어온 순서대로 나오는 데이터 구조입니다.
  • 큐는 공유자원이지만 안전하게 공유됩니다. 순서대로 꺼낼 수 있기 때문입니다.
  • 모든 작업은 같은 타임라인 안에서 처리되기 때문에 순서가 관리됩니다.
  • 따라서 큐 자료구조를 이용해서 현재 코드를 개선할 수 있습니다.

큐 선언하기

tsx
function add_item_to_cart(item) {
  cart = add_item(cart, item);
  update_total_queue(cart);
}

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);
    });
  });
}

var queue_items = [];

function update_total_queue(cart) {
  queue_items.push(cart);
}
  • 자바스크립트에서는 큐 자료구조를 지원하지 않기 때문에 배열을 이용해서 구현합니다.
  • 큐를 추가해주고 add_item_to_cart 이 호출될때마다 큐에 추가해줍니다.

큐에서 꺼내서 실행하기

tsx
var queue_items = [];

function runNext() {
  var cart = queue_items.shift();
  calc_cart_total(cart, update_total_dom);
}

function update_total_queue(cart) {
  queue_items.push(cart);
  setTimeout(runNext, 0);
}
  • 작업이 실행될때는 가장 첫번째 있는 항목을 꺼내 실행합니다.
  • 하지만 두항목이 동시에 처리될수 있으므로 이를 막아야합니다.

한번에 하나씩만 실행하기

tsx
var queue_items = [];
var working = false;

function runNext() {
  if (working) return;
  working = true;
  var cart = queue_items.shift();
  calc_cart_total(cart, update_total_dom);
}

function update_total_queue(cart) {
  queue_items.push(cart);
  setTimeout(runNext, 0);
}
  • 현재 동작하고 있는 작업이 있는지 working 으로 체크합니다.
  • 동작하고 있는 작업이 있다면 빠져나와서 한번에 하나만 실행하도록 합니다.

작업 후 다음 작업 실행하기

tsx
var queue_items = [];
var working = false;

function runNext() {
  if (working) return;
  working = true;
  var cart = queue_items.shift();
  calc_cart_total(cart, function (total) {
    update_total_dom(total);
    working = false;
    runNext();
  });
}

function update_total_queue(cart) {
  queue_items.push(cart);
  setTimeout(runNext, 0);
}
  • 작업 완료후에는 workingfalse로 하여 다음 작업이 실행할수 있게하고 runNext 를 실행하여 다음 작업을 시작합니다.

모든 작업후 종료하기

tsx
var queue_items = [];
var working = false;

function runNext() {
  if (working) return;
  if (queue_items.length === 0) return;
  working = true;
  var cart = queue_items.shift();
  calc_cart_total(cart, function (total) {
    update_total_dom(total);
    working = false;
    runNext();
  });
}

function update_total_queue(cart) {
  queue_items.push(cart);
  setTimeout(runNext, 0);
}
  • 큐가 비었을때 멈추는 코드를 추가해줍니다.

하나의 함수로 감싸기

tsx
function Queue() {
  var queue_items = [];
  var working = false;

  function runNext() {
    if (working) return;
    if (queue_items.length === 0) return;
    working = true;
    var cart = queue_items.shift();
    calc_cart_total(cart, function (total) {
      update_total_dom(total);
      working = false;
      runNext();
    });
  }

  return function (cart) {
    queue_items.push(cart);
    setTimeout(runNext, 0);
  };
}

var update_total_queue = Queue();
  • 마지막으로 queue_itemsworking 를 큐 함수로 감싸서 전역변수가 아닌 지역변수로 만듭니다.
  • 그리고 다른 곳에서 접근할수 없게 합니다.

재사용 가능한 큐 만들기

역할 분리하기

tsx
function Queue() {
  var queue_items = [];
  var working = false;

  function runNext() {
    if (working) return;
    if (queue_items.length === 0) return;
    working = true;
    var cart = queue_items.shift();
    function worker(cart, done) {
      calc_cart_total(cart, function (total) {
        update_total_dom(total);
        done(total);
      });
    }
    worker(cart, function () {
      working = false;
      runNext();
    });
  }

  return function (cart) {
    queue_items.push(cart);
    setTimeout(runNext, 0);
  };
}

var update_total_queue = Queue();
  • 현재 calc_cart_total 수행후에 DOM을 업데이트 하는 일만 수행할 수 있습니다.
  • 먼저 큐를 반복해서 처리하는 함수(runNext)와 큐에서 하는 일(calc_cart_total)을 분리할 수 있습니다.
  • done 콜백으로 큐 타임라인 작업을 이어서 할 수 있습니다.

다른 작업도 실행할 수 있도록 콜백 전달하기

tsx
function Queue(worker) {
  var queue_items = [];
  var working = false;

  function runNext() {
    if (working) return;
    if (queue_items.length === 0) return;
    working = true;
    var cart = queue_items.shift();
    worker(cart, function () {
      working = false;
      runNext();
    });
  }

  return function (cart) {
    queue_items.push(cart);
    setTimeout(runNext, 0);
  };
}

function calc_cart_worker(cart, done) {
  calc_cart_total(cart, function (total) {
    update_total_dom(total);
    done(total);
  });
}

var update_total_queue = Queue(calc_cart_worker);
  • worker 행동을 바꿀수 있도록 콜백으로 전달합니다.

작업후 실행할 콜백 전달하기

tsx
function Queue(worker) {
  var queue_items = [];
  var working = false;

  function runNext() {
    if (working) return;
    if (queue_items.length === 0) return;
    working = true;
    var item = queue_items.shift();
    worker(item.data, function (val) {
      working = false;
      setTimeout(item.callback, 0, val);
      runNext();
    });
  }

  return function (data, callback) {
    queue_items.push({
      data: data,
      callback: callback || function () {},
    });
    setTimeout(runNext, 0);
  };
}

function calc_cart_worker(cart, done) {
  calc_cart_total(cart, function (total) {
    update_total_dom(total);
    done(total);
  });
}

var update_total_queue = Queue(calc_cart_worker);
  • 작업이 끝났을때 실행하는 콜백을 추가합니다.
  • 이때 두번재 인자를 전달하지 않으면 callbackundefined가 될 수 있으므로 빈 함수를 대신 실행합니다.

큐를 건너 뛰도록 만들기

타임라인 사이에 자원 공유하기

  • 현재 상태에서는 큐에서 꺼내 작업을 할때마다 매번 DOM 업데이트를 하고 있습니다.
  • 그리고 장바구니의 최종 가격 계산은 모든 장바구니 추가후 한번만 실행해도 됩니다.

개선하기

tsx
function DroppingQueue(max, worker) {
  var queue_items = [];
  var working = false;

  function runNext() {
    if (working) return;
    if (queue_items.length === 0) return;
    working = true;
    var item = queue_items.shift();
    worker(item.data, function (val) {
      working = false;
      setTimeout(item.callback, 0, val);
      runNext();
    });
  }

  return function (data, callback) {
    queue_items.push({
      data: data,
      callback: callback || function () {},
    });
    while (queue_items.length > max) queue_items.shift();
    setTimeout(runNext, 0);
  };
}

function calc_cart_worker(cart, done) {
  calc_cart_total(cart, function (total) {
    update_total_dom(total);
    done(total);
  });
}

var update_total_queue = DroppingQueue(1, calc_cart_worker);
  • max 이상의 아이템이 큐에 들어온다면 모두 버리고 하나의 작업만 수행합니다.
  • 이를 통해 장바구니의 가격 계산은 마지막 한번만 이루어지게 됩니다.

다이어그램으로 알아보기

타임라인 사이에 자원 공유하기

  • 클릭 이벤트 내에 있는 cart는 공유자원은 동기적으로 실행되므로 문제가 되지 않습니다.
  • 큐 공유 자원은 추가와 꺼내는 작업의 실행순서가 섞일 수 있지만 모두 올바른 결과가 오는데, 동시성 기본형인 큐가 보장해줍니다.
  • DOM 업데이트는 큐에서 동기적으로 실행됩니다.
  • 여러 DOM 업데이트가 추가되더라도 마지막 하나의 작업만 실행되고 버려집니다.

정리하기

  • 공유 자원(특히 DOM/전역 상태)을 안전하게 다루려면 실행 순서 보장이 먼저입니다.
  • 간단한 큐 + working 플래그 만으로도 “한 번에 하나씩”을 강제할 수 있고, worker를 분리하면 재사용 가능한 패턴이 됩니다.