---
layout: post
title: "자잘한 도움말 #8 외부 변수가 바뀌길 기다리는 JS Promise 만들기"
subtitle: 혹은 스레드를 잡아먹지 않는 자바스크립트 무한 루프 만들기
description: 외부 변수가 바뀌길 기다리는 JS Promise 만들기, Web API, 자바스크립트 실행 환경, Javascript Runtime
date: 2023-04-14 23:46:00 +0900
categories: [Micro]
---

## TL;DR

- `while(!GlobalStore.isDone) {} resolve(true);` **아닙니다.**
- `setTimeout()`을 쓴다.

```javascript
// Ref. https://stackoverflow.com/questions/72223903/javascript-wait-until-variable-is-set
const waiter =
  new Promise<boolean>((resolve, reject) => {
    const loop = () => (GlobalStore.isDone ? resolve(true) : setTimeout(loop));
    loop();
  });
```

- Javascript는 단일 스레드에서 실행된다.  
  - Promise 객체로 무한 루프(`while (true) {}`)를 포장해 비동기 처리가 될 것 처럼 보여도  
    결국 새 스레드가 생성된 것은 아니므로 무한 루프는 자바스크립트 엔진을 잡아먹는다.
- [You need to start thinking in EVENT LOOP](https://stackoverflow.com/questions/71866925/how-to-run-multiple-infinite-loops-without-blocking-each-other-node-js)

## 이하 그리 중요하진 않은 내용들

### 무한 루프

![무한 루프](https://i.postimg.cc/6qnpJnsj/image.jpg)  
{: .center}

세상에 무조건 나쁜 것은 잘 없다. 무한 루프는 반복문의 잘못된 사용으로 컴퓨터 프로그램이 끝없이 동작하는 것을 말하는데, 위키피디아의 ["같이 보기"](https://ko.wikipedia.org/wiki/%EB%AC%B4%ED%95%9C_%EB%A3%A8%ED%94%84#%EA%B0%99%EC%9D%B4_%EB%B3%B4%EA%B8%B0) 단락에 참조되어 있듯이 일반적으로 **버그**로 간주된다. 하지만 우리는 이 순간에도 무한 루프를 이용하고 있다. 지금 당신의 OS는 무한 루프 속에서 키보드 입력을 기다리고 있다. 당신의 웹 브라우저는 이 글을 화면에 보여주면서 무한 루프 안에서 마우스 휠이 돌아가기를 기다리고 있다. 의도되지 않은 무한 루프는 버그지만, 의도된 무한 루프는 기능이다. 오늘은 자바스크립트가 가지고 있는 아주 유명한 루프, **이벤트 루프**와 이벤트 루프의 동작을 이용해 특정 값이 변하기를 기다리는 Promise 객체를 만드는 방법에 대해서 알아보자.

### Javascript가 사는 세상

자바스크립트는 싱글 스레드 언어다. 그렇다면 어째서 싱글 스레드에서 돌아가는 자바스크립트는 내 30초 짜리 AJAX 요청이 완료될 때 까지 프로그램을 정지시키지 않는 것일까?

![자바스크립트 실행 환경](https://i.postimg.cc/SK06Bwd5/Javascript-Runtime.png)  
상당히 많은 부분을 축약했지만 대략적으로 이런 구조
{: .center}

다시, 자바스크립트는 싱글 스레드 언어다. 하지만 우리는 이 말의 함정을 피해갈 수 있어야 한다. DOM을 렌더링하는 것은 우리가 짠 자바스크립트 코드가 아니다. 사용자의 UI 조작을 감지하는 것, 실제 HTTP 요청을 보내는 것은 사실 우리의 자바스크립트가 아니다. **모든 것은 브라우저일지니**. 자바스크립트는 브라우저 속의 **자바스크립트 실행 환경(Javascript Runtime Environment)**에 살고 있을 뿐.

### Web API 혹은 Browser API

자바스크립트는 기존의 정적이었던 웹 페이지가 동적으로 브라우저를 조작할 수 있도록 글루 코드로 설계된 언어다. 즉 자바스크립트가 가장 잘하는 일은 브라우저에게 일을 떠넘기고 그 결과를 받는 것이다. 자바스크립트가 남에게 일을 시킬 때 사용하는 명령 모음집을 바로 **Web API** 라고 한다. Web API는 조금 광범위한 느낌인데, **Browser API**라는 표현이 좀 더 와닿을지 모르겠다. 둘을 구분한다 해도 어쨌든 자바스크립트 입장에서 기대하는 기능은 같다고 볼 수 있겠다.

![브라우저의 구조](https://i.postimg.cc/dVSFPTGt/browser.png)  
자바스크립트는 브라우저의 이 쯤에 위치한 채 API로 브라우저에게 명령을 내린다. ([이미지 출처](https://web.dev/howbrowserswork/))
{: .center}

우리가 자바스크립트로 만든 30초 짜리 AJAX 요청은 사실 자바스크립트의 입장에서 네트워크 API를 호출한 것이고, 브라우저의 Networking 영역에서 실제 요청을 보내고 응답을 받는 것이다.

### 이벤트 루프

![이벤트 루프](https://i.postimg.cc/3wBQF8Tm/Event-Loop.png)  
{: .center}

그리고 그 30초 짜리(라고 했지만 정확하게 30초일지 29초일지 예상할 수 없는) 요청의 응답을 가지고 해야 할 일을 명시한 것이 **콜백 함수**이다. 웹 API의 결과가 담긴 콜백 함수는 자바스크립트 실행 환경의 작업 큐(Task Queue)에 저장된다. 말했듯이 자바스크립트는 이 작업이 정확하게 언제 끝나는지 예상할 수 없기 때문에 큐에 담긴 콜백 함수를 알아서 가져올 수 없다. 여기에서 **이벤트 루프**가 등장한다.

이벤트 루프는 자바스크립트 엔진 속의 호출 스택(Call Stack)이 비어있는지, 즉 싱글 스레드에서 돌아가는 자바스크립트 엔진이 현재 한가한지 확인한다. 자바스크립트 엔진이 지금 할 일이 없어 보인다면 이벤트 루프는 작업 큐에 들어 있는 가장 최우선 콜백을 호출 스택에 전달한다. 자바스크립트 엔진은 비로소 자신이 아까 실행한 AJAX 요청이 끝났음을 인지하고 다음 작업을 수행할 수 있게 된다.

### 특정 값이 바뀌길 영원히 기다리는 Promise

이번 글을 시작하게된 원인, 작년 프로젝트였다. 나는 생성된 이후로 계속 기다리다가 어떤 외부 값이 변경되었을 때 `resolve()`를 호출하는 프로미스가 필요했다. 나는 다음 코드를 작성했다:

```javascript
const waiter =
  new Promise<boolean>((resolve, reject) => {
    while (!GlobalStore.isDone) {} // isDone이 false인 동안 무한 대기
    resolve(true);  // true가 되면 무한루프를 빠져나오겠지? (*멍청한 웃음소리*)
  });
```

![기다려](https://i.postimg.cc/rs8G3Rfy/20210623-60d2f2c8010ec.gif)  
50만번 기다리면 사료 먹음(거짓말)
{: .center}

일단 [프로미스를 생성할 때 전달한 콜백은 즉시 실행된다는 사실](https://stackoverflow.com/questions/42118900/when-is-the-body-of-a-promise-constructor-callback-executed)을 짚고 넘어가자. `waiter` 프로미스가 생성된 순간 자바스크립트 엔진은 무한 루프를 실행한다. 자바스크립트 엔진의 제어 흐름은 이제 `while`에 갇히게 되었다. 호출 스택은 절대 비지 않을 것이다. `isDone`이 `true`가 될 틈은 결코 찾아오지 못할 것이다. 첫 번째 시도는 보기좋게 실패.

### 특정 값이 바뀌길 기다리는 Promise

[You need to stop thinking in loops but start thinking in **event loop** instead.](https://stackoverflow.com/questions/71866925/how-to-run-multiple-infinite-loops-without-blocking-each-other-node-js).

자바스크립트 실행 환경의 동작을 생각하자. 우리는 싱글 스레드로 돌아가고 있는 자바스크립트 엔진 안에 무한 루프를 생성하는게 아니라, 이미 존재하는 루프, 이벤트 루프를 활용해야 한다.

1. `isDone`이 `true`이면 `resolve()`를 호출한다.
2. `isDone`이 `false`이면 `setTimeout()` 함수에 1., 2. 과정을 담고 있는 함수를 콜백으로 전달한다.
3. 1., 2. 과정을 담고 있는 함수를 실행한다.

```javascript
// Ref. https://stackoverflow.com/questions/72223903/javascript-wait-until-variable-is-set
const waiter =
  new Promise<boolean>((resolve, reject) => {
    const loop = () => (GlobalStore.isDone ? resolve(true) : setTimeout(loop));
    loop();
  });
```

지정한 시간이 지나면 콜백을 실행하는 함수 `setTimeout()`는 대표적인 Web API 중 하나다. 이번엔 무한 루프를 만들지 않고 Web API를 사용해 변수값을 판단하는 함수를 재귀적으로 전달했다. 무한 루프를 사용하지 않음으로써 싱글 스레드가 잡아먹히지 않는다. Web API의 결과는 작업 큐에 담긴다. 작업 큐에 담긴 콜백 함수는 이벤트 루프에 의해 다시 호출 스택으로 전달된다. 자바스크립트 엔진은 호출 스택에 들어온 콜백 함수를 통해 다시 `isDone`의 값을 확인한 후 더 기다릴지 `resolve()`를 호출할지 결정한다. 이렇게 스레드를 잡아먹지 않으면서 `isDone`이 변경될 때 까지 기다릴 수 있는 프로미스를 만들었다.

## 내가 배운 것

- 자바스크립트 실행 환경
  - NodeJS가 왜 스스로 실행 환경(Runtime)을 지칭하는지 이제 이해가 된다.
- 브라우저의 구조
- 자바스크립트는 싱글 스레드가 맞습니다.

최근 들어서 나름 프론트엔드 한다고 설치고 다니는 것 치고 이쪽 기초가 부족하단 생각이 자주 든다. 다들 [이 글](https://d2.naver.com/helloworld/59361)을 한 번 읽어보자. CS 단골 질문으로 알려진 "주소창에 URL을 입력했을 때 무슨 일이 일어나나요?"에 대한 답변이 될 수 있을 것 같다. 난 아직 다 못읽었다.

그리고 자바스크립트 실행 환경에 대한 상단의 이미지는 앞서도 말했지만 아주 축약된 형태로, [이런 글](https://vahid.blog/post/2021-03-21-understanding-the-javascript-runtime-environment-and-dom-nodes/) 같은 사이트를 찾아서 더 공부해보자.

그리고 ChatGPT에게 이 글의 결과로 나온 코드를 개선해달라고 질문하니까 다음 코드를 만들어주더라.

```javascript
const MAX_ITERATIONS = 10000;

const waiter = new Promise<boolean>((resolve, reject) => {
  let iteration = 0;
  const loop = () => {
    iteration++;
    if (GlobalStore.isDone) {
      resolve(true);
    } else if (iteration < MAX_ITERATIONS) {
      sestTimeout(loop);
    } else {
      reject(new Error(`Waiter exceeded maximum iterations (${MAX_ITERATIONS})`));
    }
  };
  loop();
});
```

기술 발전이란 무섭구나. 아직 취직도 못했는데 벌써 인공지능한테 일자리를 뺏길 위험에 처한 기분...은 아직 안든다.

![히~하](https://i.postimg.cc/vHpXMc1t/2023-04-14-233556.png)  
{: .center}

... 아무튼 위기감을 조금은 갖고 열심히 해보자. 지금은 그저 네이버 코테도 못치고 서류 탈락을 당해서 조금 멘탈이 아플 뿐.
