비동기 프로그래밍

다시 공부하니까 뭔가 길이 보이는 것 같은 기분입니다. 이런 개념이 어디에 쓰이는지.. 왜 옛날엔 보이지 않았을까요
아무튼 오늘 정리할 비동기 프로그래밍은 부스트캠프 챌린지에서 접하고 멘탈이 으스러졌던 부분입니다. 머리로는 이해가 ‘살짝’ 되었지만 구현하려고 하니 마음대로 제어가 안됐던 기억이 있네요.

JavaScript V8 Engine

image
위에서 본 이미지네요. 자.. 자바스크립트 엔진은 자바스크립트 코드를 실행하는 프로그램 혹은 인터프리터를 말합니다. 이 중 V8은 오픈소스로, 구글에서 C++로 작성한 프로젝트입니다.
V8은 웹 브라우저 내부에서 자바스크립트의 수행 속도의 개선을 목표로 고안되었으며, 속도 향상을 위해 인터프리터 사용 대신 자바스크립트 코드를 머신 코드로 컴파일합니다. 다만, 바이트 코드와 같은 중간 코드를 생산하지 않습니다.
V8 엔진은 Chrome과 Node.js에서 사용되며, 다음과 같이 두 부분으로 구성됩니다.
image
메모리 힙은 메모리 할당이 이루어지는 곳이고, 콜 스택은 코드가 실행되면서 스택 프레임이 쌓이는 곳입니다.

브라우저에는 거의 모든 자바스크립트 개발자가 사용하는 API가 있습니다. 하지만 이들은 자바스크립트 엔진이 아니라 브라우저가 제공하는 Web API에서 제공됩니다. 자바스크립트의 런타임-자바스크립트가 구동되는 환경-은 다음과 같습니다.
image
자바스크립트는 싱글 스레드 프로그래밍 언어입니다. 따라서 콜 스택이 하나고, 한 번에 하나의 일만 처리할 수 있습니다. 콜 스택은 기본적으로 우리가 프로그램의 어디에 있는지를 기록하는 자료구조입니다. 우리가 함수 안으로 들어가는 순간 해당 함수를 이 스택의 제일 위에 놓게 됩니다. 스택에 쌓이는 것들을 스택 프레임이라고 하며, 함수 실행이 끝나면 스택의 가장 위에 있는 스택 프레임이 제거됩니다.
싱글 스레드 환경은 편합니다. 하지만 특정 코드의 실행이 늦어진다면 다른 코드들의 실행이 같이 늦어지는 현상이 발생할 수 있습니다. 따라서 브라우저에서 응답없음 오류를 발생시킬 수 있습니다. 이런 문제를 해결하기 위해 ‘비동기 콜백’을 도입하여 브라우저가 응답없음 상태에 빠지지 않게 하면서도 무거운 코드를 수행합니다.

비동기

동기 방식은 하나의 요청이 처리되는 동안 다른 요청은 기다려야 하는 방식인데, 이런 방식에서 기다리는 문제를 스레드로 처리했습니다. 이 문제를 스레드말고 비동기 방식으로도 처리할 수 있는데, 요청을 보내놓고 처리를 기다리지 않고 넘겨버리는 것입니다. image
노드 JS는 비동기를 지원하고 하나의 스레드로 동작하는 방식입니다. 하나의 스레드로 동작하기 때문에 단일 호출 스택을 이용해 순차적으로 처리하게 되는데, 요청을 동시에 받을 수 있는 것은 노드 JS의 비동기가 이벤트 방식으로 사용되기 때문입니다.
비동기 호출을 위해 사용되는 함수들은 JavaScript 엔진이 아니라 Web APIs 영역에 있습니다. Event Loop와 함수 요청을 처리하는 Task Queue도 엔진 밖에 구현되어 있습니다. 클라이언트의 요청을 비동기로 처리하기 위해 이벤트가 발생하고 서버 내부에 메시지 형태로 전달됩니다. 서버 내부에서 이 메시지를 Event Loop가 처리하게 되는데, 처리하는 동안 제어권은 다음 요청으로 넘어가고 완료시 Callback을 호출합니다.
Task Queue는 콜백 함수들이 대기하는 queue 형태의 배열입니다. Event Loop는 Task Queue와 Call Stack이 비워졌는지 여부를 계속 확인합니다. Call Stack이 비워졌다면 Task Queue에서 콜백 함수를 가져와 실행합니다. 비동기 처리를 하는 함수는 Task Queue로 넘어가서 대기하고 Call Stack에서 요청을 처리합니다.

자바스크립트에서의 비동기 처리

  • 비동기 처리란 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성입니다.
  • 비동기 처리의 가장 흔한 사례는 제이쿼리의 ajax입니다. 화면에 표시할 이미지나 데이터를 서버에서 불러와 표시할 때, ajax 통신으로 해당 데이터를 서버로부터 가져올 수 있습니다.
  • 비동기 처리의 두 번째 사례로는 setTimeout()이 있습니다. setTimeout()은 Web API의 한 종류로, 코드를 바로 실행하지 않고 지정한 시간만큼 기다렸다가 로직을 실행합니다.
      console.log('Hello');
      setTimeout(function(){
          console.log('Mukho');
      }, 3000);
      console.log('Hi');
    
      /* 출력
      Hello
      Hi
      Mukho // 3초 뒤 출력됨
      */
    
  • 상단의 코드를 비동기 처리를 모르는 사람이 볼 경우, ‘Hello - 3초 뒤 Mukho - Hi’ 순으로 출력된다고 할 것입니다. 저도 그랬거든요…
  • 콜백 함수는 다른 함수가 실행을 끝낸 뒤 실행되는, callback되는 함수입니다.
  • ‘Hello’와 ‘Hi’는 자바스크립트 엔진의 콜 스택에 저장되고, setTimeout은 Web APIs의 요소이기 때문에 Task Queue에 저장됩니다. 따라서 우선 자바스크립트의 콜 스택에 있는 것들이 우선 실행되고, 이후에 Task Queue에 저장된 것들이 처리됩니다.
  • 콜백 함수를 사용하지 않으려면 Async나 Promise를 사용해야 합니다.

Async & Await + Promise

  • Promise는 자바스크립트 비동기 처리에 사용되는 객체입니다. Pending(대기), Fulfilled(이행), Rejected(실패)의 세 가지 상태가 있으며, resolved()를 실행하면 Fulfilled 상태가 됩니다. Fulfilled 상태가 되면 then()을 사용하여 처리 결과 값을 받을 수 있다.
  • AsyncAwait은 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다. 기존의 비동기 처리 방식인 Callback Function과 Promise의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와준다.
  • async 키워드가 붙은 함수 내부에서 비동기 처리를 하는 함수에 await 키워드를 붙입니다. 그러면 await 키워드가 붙은 함수가 다 처리될 때 까지 async의 다음 부분들은 대기합니다. 이때, await 키워드가 붙은 비동기 처리 메소드는 꼭 Promise 객체를 반환해야 합니다.
  • 다음 예시는 연락처를 출력하는 예제를 동기적으로 처리한 방식이다.
function getPhoneNumber(){
	return new Promise(function(resolve, reject){
		let phoneNumber = '010-1234-5678';
		resolve(phoneNumber);
	});
}

async function printPhoneNumber(){
	let phoneNumber = await getPhoneNumber();
	console.log(phoneNumber);
}

printPhoneNumber(); // '010-1234-5678'

참고