FE/BOOK

[함수형코딩]WEEK9 순서가 없을때, 질서를 만드는 방법

<zinny/> 2025. 5. 21. 19:48
728x90

 

지난 주에 타임라인 다이어그램이라는 개념을 읽었다.

비동기 환경에서 작업 순서를 시각화 하는 이 다이어그램은,

흐름을 이해하고 문제를 디버깅 하는데 유용한 친구였다.

 

하지만 단순히 다이어그램만 그린다고 문제가 사라지는 것은 아니다. 

이번 주에는 그 흐름을 어떻게 제어할 것인가,

즉 실제 문제를 해결하는 방법에 대해서 정리할 것이다.

⁉️ 자바스크립트에 큐 자료구조가 없다?

사실 나도 처음 알았다. 

자바스크립트에는 Queue라는 내장 자료구조가 없다.

 

큐 자료구조는 간단하게 설명하면 선입선출(FIFO)을 의미하는데,

다행히 배열 메서드로 선입 선출 방식의 큐를 흉내낼 수 있다.

const queue = [];

queue.push("A"); // 데이터 넣기
queue.push("B");

console.log(queue.shift()); // A

- push() → 뒤에 추가

- shift() → 앞에서 꺼냄

 

주의할 점은 shift()는 배열의 맨 앞 요소를 제거하면서 나머지 요소를 전부 앞으로 밀어내기 때문에 성능상 비용이 크다.

그래서 고성능이 필요한 상황에서는 큐를 직접 구현하거나, linked list 구조로 대체하는게 더 좋다고 한다. 

 

서론이 조금 길었지만 이 이야기를 왜 시작했냐면...

비동기 환경에서 처리를 하기 위해서 이다. 

⚙️ 비동기 환경에서 큐가 필요한 이유

📌 예시

- A와 B라는 두 작업이 같은 자원(예: 장바구니)을 동시에 수정한다면?

→ 순서가 꼬이거나, 데이터가 중복되거나, 충돌이 일어날 수 있다.

 

이럴 때는 큐를 사용해서 자원 접근을 순차적으로 제어해야 한다

 
function Queue() {
    const queue_item = [];
    let working = false;
    
    const runNext = () => {
    	if(working) return;
        if(queue_item.length === 0) return;
        
        working = true
        const cart = queue_item.shift();
        calc_cart_total(cart, (total)=>{
            update_total_dom(total);
            working = false;
            runNext();
        })
    }
    
    return (cart) => {
    	queue_item.push(cart);
        setTimeout(runNext, 0);
    }
}

 

비동기 작업을 한 번에 하나씩 처리하게 되므로, 동시성 문제를 방지할 수 있다. 


만약, API 요청이 너무 많아 한꺼번에 처리할 수 없는 상황이 생기면?

➡ 이럴 땐 모든 요청을 기다리게 만들기보단, 일부 요청은 과감히 버리는 전략이 필요하다.

 

📌 예시

function DroppingQueue(limit) {
  const queue = [];
  let working = false;

  const runNext = () => {
    if (working) return;
    if (queue.length === 0) return;

    working = true;
    const task = queue.shift();
    task(() => {
      working = false;
      runNext();
    });
  };

  return (task) => {
    if (queue.length >= limit) {
      console.warn("Queue is full. Dropping task.");
      return;
    }
    queue.push(task);
    setTimeout(runNext, 0);
  };
}

- 제한된 용량을 가진 큐를 만들고

- 꽉 차면 새 작업은 거절하기

- 과부화 상황에도 안정적인 동작을 유지한다.

 

이런 구조가 왜 중요한가?

- 실시간 서비스는 안정성 보다는 응답속도가 중요한 경우가 많다

- 일정한 처리 속도를 유지하면서 시스템 과부화를 방지한다.

 

✂️  타임라인 커팅

작업이 병렬로 실행될 수도 있지만,

특정 시점에서는 순서를 맞춰야 할 때도 있다.

 

우리는 이것을 타임라인 커팅이라고 부른다. 

 

📌 예시

- A, B, C 작업은, B가 끝나야 A와 C가 실행될 수있다. 

await Promise.all([doA(), doB(), doC()]);
// 모든 작업이 끝난 뒤 다음 단계로

// 함수형 스타일
go(
  fetchData,
  safeParseJson,
  when(isValid, processData)
);

 

흐름은 비동기 이지만,

의도한 시점에 잘라내듯 특정지점에서 동기화 하는 설계가 필요하다.


 

🔈 

사실 나는 이미 async/await를 자연스럽게 쓰고 있었지만,
오늘 큐를 직접 만들고 타임라인 흐름을 시각화해보니
내가 그동안 얼마나 많은 비동기 처리를 “당연하게” 넘겨왔는지 돌아보게 됐다.

 

await는 단순한 키워드가 아니라,
내 코드 안에 타임라인 커팅 지점을 새기는 설계 도구라는 걸 느꼈다.

앞으로는 동작 순서를 예측이 아니라 설계하는 자세로 코드를 짜야겠다는 생각이 들었다.

728x90