본문 바로가기
개발공부일지/React

React - JavaScript 응용하기 ( 동기 & 비동기)

by Hynn1429 2023. 8. 6.
반응형

안녕하세요.

Hynn 입니다.

 

이번 포스팅에서는 JavaScript 에서 가장 주의깊게 알아야 할 동기, 비동기에 대해서 알아보도록 하겠습니다.

이 동기와 비동기는 JavaScript 가 구동되는 엔진의 원리에도 이해가 필요합니다.

 

물론 깊은 부분은 별도의 포스팅으로 다룰 예정입니다.

여기서는 실제 사용하는 코드 위주로 알아보도록 하겠습니다.

 

1. 동기와 비동기 이해하기

 

먼저 아래의 3개의 task Function 이 있다고 가정해보도록 하겠습니다.

const taskA = () => {
  const obj = { message: 'taskA' }; 
  console.log(obj.message);
};

const taskB = () => {
  setTimeout(() => {
    const obj = { message: 'taskB' }; 
    console.log(obj.message);
  }, 0);
};

const taskC = () => {
  const obj = { message: 'taskC' }; 
  console.log(obj.message);
};

taskA();
taskB();
taskC();

이 3개의 함수를 동작하는 순서를 이해하기 위해서는 아래의 용어들의 대한 이해가 필요합니다.

 

구성 요소 설명
Call Stack 현재 실행중인 함수가 Call Stack 에 쌓입니다. 함수가 호출되면 Call Stack 에 위에 추가가 되고, 실행이 완료가 되면 Call Stack 에서 제거됩니다.
Heap 객체가 저장되는 메모리 영역입니다. JavaScript 의 변수, 객체는 Heap 에 저장됩니다.
Event Loop Event Loop 는 Callback Queue 와  Call Stack 을 실시간으로 확인합니다. 만약 이벤트 루프가 확인 시 Call Stack 이 비어있고, Callback Queue 에 대기중인 함수가 존재한다면, 함수를 Call Stack 으로 이동시키는 역활을 담당합니다. 
Callback Queue 비동기 작업에 대한 콜백 함수가 대기하는 곳입니다. Call Stack 이 비어있을 대, Event Loop 에 의해 Call Stack 으로 이동합니다.
Garbage Collection 프로그래밍언어에서 자동 메모리 관리의 일부로, 사용 중 메모리 중 더 이상 필요하지 않은 메모리를 자동으로 해제하는 프로세스
Web APIs  이는 "브라우저" 에서 제공하는 API 로서, HTTP 요청, 타이머 설정과 같은 비동기 작업을 처리합니다. 이 작업이 완료되면 콜백함수가 Callback Queue 로 이동합니다. 

 

1) 동기

 

일반적으로 동기작업의 경우 간단하게 처리가 이루어집니다.

JavaScript 엔진에서 함수를 실행하면 동기 함수의 경우, 바로 Call Stack 으로 이동합니다.

이 이동과정에서 함수의 원시데이터가 아니라 객체와 같은 복잡성이 있는 데이터가 있다면, 이 데이터는 Heap 에 저장됩니다.

 

이 시점부터 Heap 에 할당된 메모리는 Garbage Collection 에서 관리를 하게됩니다.

Garbage Collection 이 처리하는 메모리 할당 해제는 1가지의 핵심사항을 이해해야 합니다.

 

Heap에 저장된 객체를 참조를 해야하는지, 더 이상 참조를 할 필요가 없는지에 따라 결정되는 것입니다.

이를 간단하게 표현하면 아래와 같습니다.

 

const taskA = () => {
  const obj = { message: 'taskA' }; 
  console.log(obj.message);
};
taskA();


//........

let globalObj;

const taskA = () => {
  globalObj = { message: 'taskA' }; 
  console.log(globalObj.message);
};

taskA();

위의 차이를 보게된다면, 첫번째 예시의 함수는 taskA 에 obj 라는 변수에 객체가 담깁니다.

이는 Code-Block 상, taskA 블록에 할당된 변수입니다.

즉, 이 변수는 taskA 가 실행이 완료되면, 내부의 변수는 더 이상 사용할 필요가 없습니다. 이 경우에는 Garbage Collection 의 처리해야할 대상으로 인지하게 됩니다. "더 이상 참조"를 할 필요가 없는 변수가 Heap 에 존재해야 할 이유가 없기 때문입니다.

 

반면에, 두번째 예시인 globalObj 는 전역변수로서, taskA 함수가 실행이 끝나더라도, 전역상태의 변수로서 언제든지 "참조" 할 수 있는 변수가 됩니다. 이 경우에는 Garbage Collection 의 대상이 아닙니다.

 

따라서 이러한 이해를 한다면 Heap 에 저장되는 구조와, Garbage Collection 이 JavaScript 에서 어떻게 메모리를 관리하는지를 개략적으로 이해할 수 있을 것이라고 생각합니다. 

 

2) 비동기

 

반면에 비동기의 경우, 단계를 조금 더 거쳐야 하는 작업이 이루어집니다. 

먼저 비동기 작업의 경우, 함수를 호출하게 되면, 아래의 순서대로 이루어 집니다.

그림을 글로 풀어서 설명하면 아래와 같습니다.

  • 위의 예시에 작성된 함수의 순서대로 본다면 taksB 함수가 호출됩니다.
  • 예시에서 호출된 함수의 내부에 setTimeout 을 처리하기 위해 WebAPI 에서 지정된 시간동안 대기합니다. 
  • 지정된 시간의 대기가 완료된(비동기 작업) 이후에는 Callback Queue 로 함수가 이동합니다.
  • Event Loop 는 이제 Call Stack , Callback Queue 를 확인하면서, Call Stack 이 비어 있을때, Event Loop 는 Callback Queue 에 있는 함수를 Call Stack 으로 이동시킵니다.
  • 이제 Call Stack 에서 함수를 실행하여 완료시키고, 실행이 완료되면 Call Stack 에서 제거됩니다.

물론 동기로 동작하는 개념처럼, Heap 에서 객체와 같은 복잡성 있는 데이터를 저장하는 것이나, Garbage Collection 이 담당하는 영역은 같습니다.

 

하지만 비동기의 경우, 동기에 비해서 보다 여러가지의 단계를 거치므로 이 차이를 인지해야 합니다.

이는 별도의 포스팅으로 조금 더 자세하게 소개시켜드릴 수 있도록 하겠습니다.

 

 2. JavaScript 의 싱글 스레드 동작 방식

JavaScript 는 기본적으로 "싱글 스레드" 방식의 동작을 지원합니다.

동기로서 실행되는 코드만을 본다면 하나의 코드가 Call Stack 에서 실행이 완료되어야 다음 코드가 실행되는 방식을 취하고 있습니다.

즉, 하나의 스레드에서는 하나의 작업을 처리를 하고, 작업이 처리가 완료가 되면, 다음 코드/작업을 실행하는 방식을 가지고 있습니다.

 

이를 해결하는 가장 손쉬운 방법은 바로 멀티 스레드 방식의 동작을 지원하면 문제가 해결되는 것이 아닐까요?

하지만 JavaScript 의 싱글 스레드 방식의 동작을 우리가 바꿀수는 없습니다.

 

물론 이후에 자세하게 포스팅에서 다룰 추가적인 부가요소로 멀티 스레드 처럼 동작하게 할 수 는 있지만,

본질은 변하지 않는 싱글 스레드 방식입니다.

이 Single Thread, Multi-Thread 라는 것은, 아마 PC 하드웨어를 한번쯤 조립해보거나, 컴퓨터에 관심이 있으신분들은 한번 쯤 들어보았을 것이라고 생각합니다.

바로 CPU 라는 부품에서 말이죠.

 

이 방식의 이해는 아래의 그림으로 대체하는것이 가장 쉽고 이해가 빠를 거 같아 하나 가져와봤습니다.

 

위의 그림으로는 이해가 어려우신 분들을 위해 간단한 설명을 드리자면 아래와 같은 비유가 좋을 거 같습니다.

대표적으로, 우리가 음식점을 가서 음식을 주문을 한다고 가정해봅시다.

 

음식점에서 주방에 조리하는 분이 한분이라고 가정하고, 10개의 음식을 주문한다고 할때, 음식점마다 스타일이 다를 수는 있지만, 대체적으로 조리를 동시에 하게 됩니다.

예를 들어, 해장국 집을 갔다고 가정하면, 음식점의 조리담당자는, 혼자서 10개의 해장국을 동시에 화구에 올려서 팔팔 끓여 조리하는 방식을 취합니다.

 

이것이 일반적으로 우리가 인지하는 멀티 스레드입니다. 혼자서 동시의 10개의 작업을 처리하는 것이죠.

반면에 어떠한 음식점에서는, 한명이 하나의 음식의 조리를 담당하게 됩니다.

즉, 하나의 음식을 조리하고, 다음 음식을 조리하고, 이러한 방식을 취하면 이것이 싱글스레드 입니다.

 

즉 싱글스레드는, 하나의 작업을 완료할때까지 다른 작업을 하지 않고, 1개의 작업이 완료된 이후 다음 작업을 수행합니다.

 

이는 일반적으로 "블로킹" 방식이라고도 불리는 방식과 매우 유사합니다.

하지만 JavaScript 는 위에서 소개드린 Event Loop 를 사용하여, 블로킹 방식의 단점을 최소화하여 비동기 코드를 실행함으로써, 효율성을 높인 언어라고 할 수 있습니다.

 

즉, 비동기방식을 처리하는 구조는, 특정 코드의 실행의 완료를 기다리지 않고 다음코드를 실행하도록 처리하는 것입니다. 예를 든다면, 우리가 JavaScript 에서 사용하는 Axios 통신과 같은 네트워크 통신이나, setTimeout, setInterval 과 같은 타이머의 비동기 작업이 있다면, 작업이 완료되는 것을 기다리는 것이 아니라, 이를 Callback Queue, WebAPIs 의 매커니즘을 사용하여 결과를 처리하고, 이 코드의 다음 코드를 실행하는 방식을 취합니다. 

 

이렇게 하면 하나의 코드가 모두 완료될때까지 기다리는 것이 아니라, 효율적으로 작업을 관리할 수 있음으로, 효율성 있는 작업을 처리하게 됩니다. 

 

 

3. Callback 의 남발로 인한 단점 

 

이러한 비동기처리는 JavaScript 를 보다 더 좋은 처리를 위한 기능으로 활용이 됩니다.

하지만 비동기 함수 내의 비동기함수가 계속 중첩된다면 어떻게 될까요?

흔히 이를 콜백지옥이라고 하는 것으로 부르고 있고, 코드상으로 구현하면 아래와같은 지옥이 펼쳐집니다.

 

console.log('Hell');

const callbackHell = () => {
  step1((value1) => {
    step2(value1, (value2) => {
      step3(value2, (value3) => {
        step4(value3, (value4) => {
          step5(value4, (value5) => {
            step6(value5, (value6) => {
              step7(value6, (value7) => {
                console.log('Hell 2');
              });
            });
          });
        });
      });
    });
  });
};

이러한 Callback 지옥이라는 것은, 해외 개발자들에게도 많은 짤/밈이 있을 정도로 잘 알려져 있습니다.

코드 자체만으로 가독성이 떨어질 뿐 아니라, 비동기를 남발하게 되면, 이는 결코 좋은 선택이라고 할 수 없기 때문입니다.

일반적으로 이렇게 코드가 중첩되면, 보여지는 부분부터의 단점이 발생합니다.

 

말 그대로 코드가 계속 들여쓰기 형식으로 많아지고, 예시와 같은 간단한 코드가 아니라면, 이 자체만으로 가독성에 심각한 영향을 미치게 됩니다.

또한, 각 코드의 예외처리(에러처리)가 매우 복잡해지고, 어려워집니다. 이렇게 Callback 함수가 중첩될 경우, 에러처리를 개별로 처리해야하고, 이렇게 하게되면 결국에 중복되는 코드가 늘어날 수 밖에 없습니다.

 

또한, 위에서 제시한 JavaScript 의 동작구조상, Callback Queue, WebAPIs 에서 처리해야할 작업량이 늘어나면서, 이러한 처리가 비효율적으로 처리될 수 있습니다. 이렇게 되면, 비동기를 사용하는 의미가 사라집니다. 즉 성능적으로 문제가 발생할 수 있기 때문입니다.

이를 해결하기 위해, JavaScript 에서는 가장 까다롭게 이해해야 한다고 하는 "Promise" 객체에 대해서 알아보도록 하겠습니다.

이는 별도의 포스팅으로 작성하도록 하겠습니다.

 

감사합니다. 

반응형

댓글