[js] alert, confirm, prompt의 특성

개요top

오늘은 window.alert에 대해 잠시 설명드릴까합니다.
alert, prompt, confirm 등이 다 비슷한 동작을 하는데, 이 함수 3종은 매우 특수한 능력이 있습니다.
이 능력은 과거 ES3.1부터 부여된 예외적인 능력인데 바로 브라우저 전체를 올스탑시킬 수 있는 능력입니다.

동기명령의 일시정지?top

ES6에 이르러서는 generator, async, await 등 다양한 코루틴이 지원되면서 동기명령을 일시중단할 수 있는 방법이 생겼습니다.
하지만 기본적으로 제어문은 중도에 중지시킬 수 없습니다.

예를 들어 다음의 for문은 살펴볼까요.

for(let i = 0; i < 10; i++);

이 for문은 중간에 절대로 중지되지 않고 한 번에 동기적(sync)으로 cpu를 블록킹하면서 실행됩니다.
이런 동기명령을 일시 중단하거나 재게하는 등의 묘기를 부릴려면 시스템 내부에서 제공하는 수단을 사용하게 됩니다.
ES6에서는 각 ECrecord에 대해 suspend, resume을 제공할 수 있는 기저 구조를 포함하고 있어 이를 활용해 CPS를 내장한 제네레이터가 성립합니다.

function *test(){
  for(let i = 0; i < 10; i++){
    if(i == 3) yield i;
  }
}

위 제네레이터 구문은 for문을 i가 3일 때 일시정지 했다가 다시 진행할 수 있는 능력을 갖고 있습니다.
이렇듯 동기명령은 언어가 허용한 특수한 상황에서만 멈출 수 있는 것이죠.

하지만! alert이나 confirm등은 이러한 동기 명령을 중간에 멈출 수 있습니다.

let i = 0;
for(; i < 10; i++){
  if(i == 3) alert(i);
}
console.log(i);

위 예제에서 alert은 3이 출력되고 그대로 멈춰있으면서 확인을 누르면 정상로적으로 10도 나옵니다.
즉 for문을 중간에 멈췄다가 다시 실행한 셈입니다.

webview구현 시의 어려운 점top

이 특성만 해도 굉장히 특수한 기능이라 안드로이드나 iOS에서 웹뷰를 구현할 때 이 부분의 처리는 굉장히 까다롭습니다.

브라우저야 브라우저가 모든 걸 통제하는데 그 안에서 alert처리를 하는 거니까 경우의 수를 통제할만 하지만,
앱 내의 웹뷰에서 alert을 호출했을 때 나머지 앱의 모든 부분에 동기명령을 중지시킬 수 있냐면 그렇지 않습니다.

따라서 웹뷰들은 이 부분의 구현을 개발자에게 맡겨버립니다 ^^;
태반의 개발자가 이걸 제대로 대응하기란 쉽지 않습니다. 앱의 나머지 부분에 동기명령을 일시정지시킬 방법이 마땅히 없기 때문입니다.
이걸 어떻게 대응하는가에 대해서는 다른 앱개발 관련 포스팅에서 살펴보는 것으로 하고 오늘은 js에 집중해보죠.

더욱 강력한 일시정지 기능top

이 얘기를 좀 더 확장해서 alert은 그저 제어문을 일시중지 시킬 수 있는 레벨이 아닙니다.
두 가지 기능이 더 있는데,

  1. 타이머를 중지시킬 수 있고
    2 .백그라운드 쓰레드를 중지시킬 수 있습니다.

우선 두 개를 구분한 이유는 setTimeout, setInterval, RAF, Promise는 전부 main쓰레드 이벤트 루프입니다.
그에 반해 fetch, XHR, web worker 등은 명백히 다른 백그라운드 쓰레드죠.

메인쓰레드의 중지동작

alert이 작동한 사이에 이들은 어떻게 되는가를 살펴보죠.

<div id="test">hello</div>
<script>
setTimeout(()=>console.log("log0"), 100);
setTimeout(()=>console.log("log1"), 5000);
alert("check");
</script>

위 코드를 실행하면 check라는 메세지와 함께 alert이 뜰텐데 아무리 기다려도 console.log가 찍히지 않습니다.
그럼 실제 작동을 중지한거면 그렇지는 않습니다. 만약 alert의 확인을

  • 1초에서 4초 사이에 눌러보면 log0은 찍히고 5초가 지난후에 log1이 찍힙니다.
  • 하지만 5초 후에 눌러주면 log0과 log1이 동시에 찍힙니다.

이 실험으로 알 수 있는 것은 실제 타이머의 작동이 중지되는 게 아니라 console.log의 동작이 스택에 쌓여있고 실행을 유보하고 있다는 것입니다. 즉 내부에서 타이머의 동작은 완료되어 정상적이라면 콜백이 수행되어야하지만 그 동작만 스택에서 쌓아서 막고 있다고 볼 수 있죠.
이 점을 잘 이해해야 합니다. alert이 동작 자체를 중지시켜준 것은 아닌거죠.
이러한 특성은 Promise, setTimeout, setInterval, requestAnimationFrame 에 다 공통적으로 해당됩니다.
따라서 비동기를 통해 UI업데이트를 예약해두고 alert을 띄워도 백그라운드의 dom이 갱신될일은 없다는 것입니다.
alert뒤에 깔린 화면은 언제나 그대로라고 봐야겠죠 ^^

백그라운드 쓰레드의 중지동작

기본적으로 완전히 동일합니다. 즉 실행은 되었지만 그 콜백은 실행되지 않는거죠.

fetch('test.html').then(_=>console.log("log1"));
alert("check");

위의 코드도 절대로 확인을 누르기 전에는 log1을 볼 수 없습니다. 이유는 간단합니다. 실행이 백그라운드에서 될지라도 워커쓰레드패턴을 통해 언제나 메인쓰레드의 콜백으로 보고되기 때문입니다.

하지만 웹워커가 atomic를 이용해 메인쓰레드의 변수를 갱신하는 경우는 콜백을 막는게 아니라서 실제로 변경되는걸 alert으로 막지 않습니다.
물론 값만 바뀌고 이를 렌더링에 반영하려면 다시 함수들을 호출해야 하는 데 이건 막히므로 화면은 갱신되지 않습니다.

결론top

alert을 비롯한 다이얼로그 계열의 기본창들이 어떻게 제어구문에 관여하는지를 살펴봤습니다. 간단히 정리하면,

  1. 동일 EC의 동기명령을 일시정지 시킨다.
  2. 모든 이벤트루프의 콜백을 스택에 쌓아둔다.

라고 할 수 있겠네요.

보다 자세한 스펙은

https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-alert

에서 참고하실 수 있습니다.

최근엔 미려한 UI를 위해 시스템에서 제공하는 기본 다이얼로그를 쓰지 않는게 대세입니다.
하지만 시스템 다이얼로그만 할 수 있는 다양한 특성을 알고 그 차이점을 이해하는 것은 기본적으로 중요합니다 ^^;