자바스크립트에서의 비동기 프로그래밍이란?

개발자 베니·2021년 10월 24일
0
post-thumbnail

비동기 프로그래밍이란?

일반적으로, 프로그램의 코드는 순차적으로 진행됩니다. 한 번에 한 가지 일을 절차적으로 수행하는데, 만약 A 함수가 다른 함수의 결과값에 영향을 받는다면, A 함수는 다른 함수의 결과값을 받을 때까지 기다려야 합니다.

이 과정이 오랜 시간 지속된다면 유저의 입장에서는 프로그램이 멈춘 것 처럼 보일 것 입니다. 맥을 사용하는 유저라면 대표적으로 무지개색 커서 (비치볼)을 알고 있을 겁니다.

이 커서는 유저에게 당신이 사용하는 시스템은 지금 멈춰서 뭔가를 기다려야 한다고 말하고 있는 것입니다. 요즘과 같이 여러개의 프로세서를 동시에 돌리는 시대에서 이러한 경험들은 효율적이지 못하고 부정적입니다. 유저의 다른 코어 프로세서에서 다른 작업들을 움직이게 하고 끝났을 때 결과를 알려줄 수 있다면, 유저가 기다리는 것은 의미가 없어집니다. 그 동안 다른 작업들을 이어가면 되기 때문입니다. 이러한 방식이 비동기 프로그래밍의 기본입니다.

이러한 작업들을 비동기적으로 실행할 수 있게끔 해주는 것은 당신의 프로그래밍 환경(웹 개발이라면 브라우저)에 달려있습니다.

Blocking

웹 앱에서 비동기 프로그래밍은 매우 유용합니다. 웹 앱이 브라우저에서 어떠한 코드를 실행하느라 브라우저에게 제어권을 넘겨주지 않는 경우를 Blocking이라고 합니다. 이러한 경우에 유저는 브라우저가 멈춰버린 것처럼 느낄 것입니다.

Blocking의 간단한 예시를 한 번 보겠습니다.

const btn = document.querySelector("button");
btn.addEventListener("click", () => {
  let myDate;
  for (let i = 0; i < 10000000; i++) {
    let date = new Date();
    myDate = date;
  }

  console.log(myDate);

  let pElem = document.createElement("p");
  pElem.textContent = "This is a newly-added paragraph.";
  document.body.appendChild(pElem);
});
// 출처 mdn

날짜를 천만번이나 계산하는 비현실적인 코드지만 Blocking의 예시로 괜찮습니다. 위 코드는 버튼을 클릭하면 새로운 메시지가 출력되는 코드인데, 천만번의날짜를 계산하고 콘솔 로그에 날짜가 나온 후에야 새로운 메시지가 출력이 됩니다.

첫 번째로 버튼을 클릭하면 버튼의 css 이펙트도 멈추고 마치 멈춰버린듯한 느낌을 받을 수 있습니다. 이벤트가 끝나기 전에 다음 동작을 실행할 수 없는 것이 Blocking이라는 것을 여기서 알 수 있습니다. 그리고 그 이유는 웹 앱이 사용자의 입력을 처리를 하느라 브라우저에게 제어권을 넘겨주지 않았기 때문입니다.

이러한 현상은 왜 발생할까요?

그 이유는 자바스크립트가 Single thread이기 때문입니다.

Single thread?

먼저 Thread의 개념부터 알아볼까요? Thread란 프로그램이 작업을 완료하는데 사용할 수 있는 단일 프로세스입니다. 각 스레드는 한 번에 하나의 작업만을 수행할 수 있습니다.

Thread 1 : A ==> B ==> C

A,B,C 각각 프로세스가 해결해야하는 작업이라고 보겠습니다. 스레드는 A가 완료가 되어야 B를 수행할 수 있고, B가 완료가 되어야 C를 수행할 수 있습니다. 앞서 말했듯이 우리 컴퓨터는 여러개의 CPU 코어를 가지고 있기 때문에, 한 번에 여러가지의 일을 수행할 수 있습니다.

Thread 1 : A ==> B
Thread 2 : E ==> F

멀티 스레드를 지원하는 프로그래밍 언어는 멀티 코어 컴퓨터의 CPU를 사용하여 여러 작업을 동시에 처리할 수 있습니다.

Javascript is single thread.

그렇지만 자바스크립트는 전통적으로 싱글 스레드를 사용하고 있습니다. 우리의 컴퓨터가 굉장히 많은 CPU를 가지고 있더라도 Main thread라 불리는 단일 스레드에서만 작업을 실행할 수 있습니다. 그렇다면 이러한 의문이 들 수도 있습니다.

"그러면 자바스크립트는 안 좋은거 아닌가?"

잠깐 위의 개념들을 쉽게 이해하게끔 정리를 해보겠습니다. 스레드는 결국 우리에게 있어서 일꾼같은 존재입니다. 사용자의 입장에서 일꾼은 많을수록 좋다는 생각을 할 수 있겠지만, 그렇지도 않습니다. 일꾼들을 분배시키는 것을 스케쥴링이라고 합니다. 많은 일꾼들이 작업을 해야하는데, 그 일꾼들을 잘 분배하는 것도 하나의 일이고 하나의 스레드가 일을 하다가 다른 스레드로 접근해야할 일이 있어도 작업을 하고있는 중이라면 또 기다려야하는 복잡한 인과관계가 생기게 됩니다.

즉, 적절한 양의 스레드는 좋겠지만, 많다고 무조건 좋은건 아니라는 겁니다.

우리 자바스크립트는 싱글 스레드 방식을 선택해서 멀티 스레드가 가지는 복잡한 부분들에서 자유로워 졌습니다. 그리고 멀티 스레드의 방식들을 부분적으로 가져와서 쓸 수 있기 때문에 단순하지만 빠르고 강력한 도구가 된 것입니다.

다시 위로 올라가서 만약에 싱글 스레드인 자바스크립트가 멀티스레드의 방식으로 위의 예시를 처리하는 방법을 한 번 알아보겠습니다.

Asynchronous

위에서 블로킹에 대한 간단한 예시를 봤었죠? 싱글 스레드인 자바스크립트의 한계였던 블로킹을 해결하는 코드를 보겠습니다.

const btn = document.querySelector("button");
const worker = new Worker("worker.js");

btn.addEventListener("click", () => {
  worker.postMessage("Go!");

  let pElem = document.createElement("p");
  pElem.textContent = "This is a newly-added paragraph.";
  document.body.appendChild(pElem);
});

worker.onmessage = function (e) {
  console.log(e.data);
};

이 예시는 정확하게는 노드의 내장 모듈인 웹 워커를 사용한 비동기 프로그래밍입니다. 웹 워커는 여러 개의 자바스크립트 청크를 동시에 실행할 수 있게끔 worker라고 불리는 별도의 스레드를 보낼 수 있습니다. 따라서 시간이 오래 걸리는 처리는 워커를 이용하면 블로킹을 막을 수 있습니다.

Main thread: Task A --> Task C
Worker thread: Expensive task B

웹 워커는 꽤나 유용하지만, 한계가 있습니다. 우선 첫 번째로, DOM에 접근할 수 없습니다. 이 말은 UI를 업데이트 하기 위한 어떠한 행동도 웹 워커는 할 수가 없다는 말이 됩니다. 웹 워커는 단순한 계산이 요구되는 상황에서만 사용할 수 있다는 겁니다.

두 번째로, 워커에서 실행되는 코드는 블로킹을 막아주지만, 동기적으로 실행이 됩니다. 이러한 것들은 함수를 사용할 때 문제가 되는데, 어떤 함수가 실행되기 위해서 이 전의 프로세스의 결과를 리턴받아야 할 경우에 문제가 생깁니다. 동기적으로 실행되면 함수 실행에 필요한 매개변수를 받아오지 못하는 경우가 생기기 때문에, 함수는 사용자가 원하는 기능을 실행할 수 없게됩니다.

Main thread: Task A --> Task B

Task A가 서버로부터 이미지를 받아오고 Task B가 그 이미지를 편집하는 기능이라고 한 번 생각해보겠습니다. A를 시작하고 결과가 받아오지 않았는데 B를 수행하게 되면 에러가 발생하게 됩니다. 필요한 이미지를 받아오지 못했는데 편집을 할 수는 없으니까요!

이러한 문제를 해결하기 위해서 브라우저를 통해 특정 작업을 비동기적으로 실행할 수 있습니다. 바로 약속 (Promises)을 사용하는 겁니다.

Main thread: Task A Task B
Promise: | async operation |

이미지를 받아오는 동안에 B가 실행되지 않게끔 약속을 해두면 에러가 발생할 일이 없고 위의 작업은 다른 곳에서 처리가 되기 때문에, 비동기 작업이 진행되는 동안에 Main thread에 블로킹이 일어나지 않습니다.

결론

실제로 프로젝트를 진행하면서 서버에서 데이터를 받아오고 함수로 사용하는 경우에 이런 작업들이 많이 일어나는데, 프로미스는 정말로 중요하게 사용이 됩니다. 웹 개발자들은 이렇게 유저가 기다려야하는 동안에 브라우저가 멈춘 것이 아니고 작업이 잘 진행되고 있다는 의미로 로딩과 같은 부분들도 많이 신경써야 합니다.

현대의 소프트웨어 설계는 프로그램이 한 번에 많은 일을 할 수 있도록 하는 비동기 프로그래밍을 중심으로 설계되고 있습니다. 새롭고 강력한 API를 이용하면서, 비동기로 작업을 해야할 일들이 많아지고 있습니다. 예전에는 비동기 코드를 작성하는 것이 분명히 어려웠지만 최근에는 많이 나아졌습니다. 물론 그렇다고 어렵지 않다고 말할 순 없겠지만요.

다음번에 작성할 글은 프로미스에 대한 글을 작성할 것 같습니다. 감사합니다.

0개의 댓글