• JavaScript의 동작원리와 비동기처리

    2021. 6. 27.

    by. KimBangg

    자바스크립트 엔진

     

    자바스크립트 엔진을 대표하는 것은 구글의 V8이 있다.

    • Memory Heap : 메모리 할당이 일어나는 곳
    • Call Stack : 코드 실행에 따라 호출 스택이 쌓이는 곳

     

    콜스택

     

    자바스크립트는 싱글 스레드로 작업을 처리한다. 즉, 1개의 콜스택만을 가지고 작업을 처리 한다는 것이다.

    콜스택은 이름에서 볼 수 있듯이, 스택의 구조를 가져 먼저 들어간 작업이 늦게 처리 되는 구조를 가지고 있다.

     

    그리하여, 아래의 코드의 결과는 세번째 -> 두번째 -> 첫번째 순서로 출력 될 것이다.

    function first(){
        second();
        console.log("첫번째");
    }
    function second(){
        third();
        console.log("두번째");
    }
    function third(){
        console.log("세번째");
    }
    first();

     

     

    런타임

     

    자바스크립트 엔진 밖에서도 자바스크립트에 관여하는 요소들이 있다. 대표적으로는 Web API, Task Queue, Event Loop 등이 존재한다. 그 중 런타임은 특정 언어로 만든 프로그램을 실행 할 수 있는 환경을 의미한다.

    • Web API : 브라우저에서 제공되는 API이다. 자바스크립트 엔진에서 지원 하지 않는 setTimeout, HTTP 요청, DOM 이벤트 등을 지원한다
    • Task Queue :  이벤트 발생 후 호출되어야 할 콜백 함수들이 기다리는 곳이다. 이벤트 루프가 정한 순서대로 줄을 서 있으며, 콜백 큐라고 부르기도 한다.
    • Event Loop : 이벤트 발생 시 호출 할 콜백 함수들을 관리하고, 홏루된 콜백 함수의 실행 순서를 결정한다.

     

    Task Queue

    자바스크립트에서 비동기로 호출되는 함수들은 콜스택에 쌓이지 않고, Task Queue에 쌓인다. 

    아래의 코드의 결과는 이와 같은 이유로 "A->C->B" 순서로 출력이 된다. 

    console.log("A");
    setTimeout(function() {
      console.log("B");
    }, 0);
    console.log("C");

     

    비동기 ( Asynchronous )

    비동기적 처리란 특정 코드가 종료 되기 전에, 다른 코드를 우선적으로 실행 하게 만드는 특징이다.

    아래의 코드를 보면 출력은 Hello -> Hello Again -> Bye 순으로 출력 될 것이다.

     

    그렇다면 왜 비동기적인 처리가 필요할까? 자바스크립트는 위에서 언급 했던 것처럼 하나의 콜스택을 가지고 있습니다.

    그러던 도중, 아래와 같이 일정 시간 이후에 작업을 처리 하고 싶어서 setTimeOut을 걸어놨는데 이를 비동기적으로 처리 해주지 않는다면 그 작업이 끝날 때 까지 기다려야하죠.

     

    아래의 코드는 3초라서 괜찮지만, 예를 들어서 9999분을 걸어놨다면 그 시간이 지날 때 까지는 아무것도 못하는 프로그램이 됩니다. 

    console.log('Hello');
    
    setTimeout(function() {
    	console.log('Bye');
    }, 3000);
    
    console.log('Hello Again');

     

    코드의 작동 순서

     

    1. 전역 Context에서 Main() 함수가 콜스택에 쌓이고, console.log("Hello")가 콜스택에 쌓입니다. 이후, Hello 가 출력됩니다.

    2. Hello 는 리턴되며 CallStack에서 제거됩니다.

    3. SetTimeOut 함수가 실행되면서 CallStack에 SetTimeOut 함수가 들어갑니다.

    4. SetTimeOut 함수는 자바스크립트 엔진에서 처리 하지 않고, Web API가 처리하므로 SetTimeOut 함수를 전달하고 작업을 요청합니다.

    5. 콜스택에서는 SetTimeOut 함수가 제거됩니다.

    6. console.log("Hello Again")가 콜스택에 쌓입니다. 이후, Hello Again이 출력됩니다. 

    7. Hello Again 는 리턴되며 CallStack에서 제거됩니다.

    8. 전역 컨텍스트에서 호출된 main() 함수가 리턴되면서, 콜스택에서 제거됩니다.

    9. Web API는 setTimeout 작업을 실행한다. 3초를 센 후 Task Queue로 Callback 함수를 보낸다.

    10. Event Loop는 콜스택이 비어 있으면 Task Queue에 있는 함수를 하나씩 꺼내 Call Stack에 넣고 실행한다.

    11. console.log(“Bye”) 이 호출되고 Call Stack에 쌓인다. “Bye”이 콘솔에 찍힌다.

    12. Bye 는 리턴되며 CallStack에서 제거됩니다.

    13. Event Loop는 Task Queue에 콜백 함수가 들어올 때까지 계속 대기합니다.

     

    콜백 함수

    실행 순서가 중요한 상황에는 순서에 맞춰 코드를 실행 해야한다. 이 때는 주로 콜백함수를 중첩하는 방법을 사용합니다.

     

    예를 들어서 위에서와 같이, setTimeOut을 이용한다거나, API 요청을 보낸 후 응답을 받아오는 경우등 바로 실행 될 수 없는 조건이 담긴 함수가 있습니다. 

    이러한 예외조건은 콜백 함수를 이용해, 실행이 끝날 때까지 특정 코드가 실행되지 않게 기다려주므로 순차적인 진행을 가능하게 합니다.

     

    아래의 코드를 통해 예시를 들어 보겠습니다.

     

    아래의 코드는 '삭제' 버튼을 누르면 역의 이름이 삭제 되는 예시입니다.

    코드를 살펴보면, isAdmin 즉 권한이 있어야만 삭제가 되는데 아래 코드를 통해 삭제가 이루어 질 수 있을까요?
    정답은 "아니요." 입니다. 실제로 ajax의 통신이 모두 이루어지기 전에, 이루어 지기에 삭제가 되지 않습니다.

    $stationDeleteButton.addEventListener('click', removeStation);
    
    const removeStation = ({target}) => {
    
    	const isAdmin = ajax();
        
        if ( !isAdmin ) return;
        
        target.remove();
        
    }

    위의 코드를 아래와 같이 바꿔주면, 콜백을 통해  ajax가 완료될 때까지 기다린 후에 출력 되기 때문에 문제 없이 삭제가 가능해집니다.

    $stationDeleteButton.addEventListener('click', removeStation);
    
    const removeStation = ({target}) => {
    
    const isAdmin = ajax();
    	
        setTimeout(() => {
        
        if ( !isAdmin ) return;
        
       	   target.remove();
        
        },500);
    }

     

    Promise

     

    프로미즈는 ES6에 추가된 문법으로써, 위의 콜백함수를 통해 발생 될 수 있는 "콜백 지옥"을 해결 하기 위해 나오게 되었습니다.

     

    promise는 resolve와 reject를 함수로 받습니다. 

     

     

    Async / Await

     

    Async

     

    함수 앞에 Async를 명시하면, promise를 생성 하지 않더라도 함수 안에 있는 code block들이 promise로 변환됩니다.

    const fetchUser = async () => {
    	return 'KimBangg';
    }

     

    Await

     

    비동기 로직이 실행 되는 동안, 작업을 멈추고 반환을 기다리는 역할을 수행합니다. 

     

    1초 후 무한 => 1초 후 도전이 출력됩니다.

    const delay = (ms) => {
    	return new Promise((resolve) => setTimeOut(resolve,ms));
    };
    
    
    const delayPrint = async () => {
    	await delay(1000);
        console.log("무한");
        await delay(1000);
        console.log("도전");
    }
    
    delayPrint();
    
    

    댓글