-
[21.06.05] 비동기 처리? 그래도 싱글 스레드인 것이 함정인 자바스크립트개발 블로깅/Javascript 개념 2021. 6. 5. 14:44
자바스크립트를 다루는 개발자라면 웹 서비스에서 필수로 쓰이고 있는 Javascript가 싱글 스레드(Single Thread) 기반으로 동작한다는 것을 알고 있을 것이다.
그리고 이러한 싱글 스레드의 한계를 넘고자, 비동기(asynchronous) 동작을 제어할 수 있는 Event Loop라는 개념이 등장하면서 AJax, 그 뒤로 Promise, async/await 등 비동기 동작에 대한 제어를 쉽게 할 수 있는 키워드가 속속히 등장하기 시작했다.
그만큼 싱글 스레드 기반임에도, 멀티 스레드 못지않은 Task 동작을 시키고 이를 더 쉽게 제어하기 위해 계속해서 발전되어 왔다.
하지만, 자바스크립트는 여전히 싱글 스레드이다.
더 Row Level 단으로 따지고 보면, 비동기적으로 동작하는 Task들도, 결국은 싱글 스레드에서 관리가 된다는 것이다.
아래 간단한 예시를 한번 봐보자.const MyPage = () => { useEffect(() => { logEvent('page_view', { type: 'visit', userID }); }, []); return { ... } } export default MyPage;
위 코드를 얼핏 보면, 특정 페이지에 접속하면 방문한 유저의 로그를 수집하는 코드일 것이라 추측할 수 있다.
수집하는 로그는 FireBase, Google Analitics 또는 Sentry가 될 수도 있겠다.
이러한 로그를 수집하는 동작은 주로 비동기 처리를 동작하게 된다. 수집 요청만 보내고 서비스는 그대로 동작시키면 되기 때문에 로그 수집에 대한 서버의 응답을 굳이 기다릴 필요가 없기 때문이다.
그러나, 이렇게 비동기 로직으로 로그 수집을 한다고 해서, 아예 자바스크립트가 신경을 쓰지 않게 되는 것은 아니다.
이게 무슨 말일까?Event Loop의 동작 원리
전형적인 Event Loop의 동작 원리를 표현한 그림이다.
비동기 로직이 호출되면 Javascript Engine 내 call Stack에서 벗어나서 따로 Web API를 거쳐 Callback Queue에 Task가 저장된다. 그리고 다시 Callback Queue 함수들을 pop 하여 Call Stack으로 하나씩 처리한다.
여기서 약간 헷갈리지 말아야 할 점은, 비동기 로직이 실제로 처리되는 부분이 어디인지 이다.console.log('console one'); fetch('https://blabla~.co.kr').then( ()=> console.log('console two'); ) console.log('console three'); // console one // console three // console two
위 코드를 보면 중간에 fetch() 함수는 비동기 로직이여서, 로직 처리 후 동작하는 로그가 이벤트 루프 동작 원리로 인해 가장 마지막에 찍히게 된다.
여기서 fetch() 함수로 서버에 데이터를 요청하는 동작은 자바스크립트 엔진 밖에서 따로 처리되도록 하고, 자바스크립트 엔진은 이후에 CallStack에 쌓여있는 Task를 계속해서 처리해 나간다.
그리고 비동기 함수 중 callback Queue로 넘어가서, Javascript CallStack이 비워지면 Queue의 Task가 넘어가는 것은 비동기 로직 처리 후 처리하는 then 쪽이다.
그래서 fetch() 함수 자체는 자바스크립트 엔진과 별개로 동작하기 때문에 어쩌면 multi Thread 형식처럼 보일 수 있다.
그러나 Javascript 자체가 Single Thread로 동작하다 보니, 서버에 요청하고 응답을 받는지에 대한 Traking을 여전히 진행하게 된다.이는 싱글 스레드의 리소스를 분명히 쓰고 있으며 성능에 영향을 끼치고 있음은 확실하다. Javascript가 훌륭한 Event Loop라는 요소로 비동기 로직 처리를 잘 제어하고 있지만, 여전히 싱글 스레드의 한계를 가지고 있을 수밖에 없는 요소이다.
그럼에도 Single Thread 기반에서 Event Loop를 통해 multi Thread 못지않게 비동기 처리가 가능하다는 것은 여전히 대단하지 않을 수 없다.
이러한 비동기 로직 처리는 모든 Task를 하나의 Main Thread(동기적 처리)로 동작시키는 것과 비교한다면, 성능면으로는 엄청난 이점이다. 그리고 이러한 장점을 이용하여 처리할 수 있도록 비동기적으로 동작하도록 하는 Web API가 계속해서 등장하고 있다.
아래에는 비동기 처리 관련하여 유용한 Web API를 몇 가지 소개해 보려고 한다.
Main Thread를 사용하지 않는 Web API 함수 소개
Intersection Observer API
특정 DOM 요소, 혹은 최상위 Document 내에서 교차영역에 대한 변화를 비동기적으로 관찰하는 API이다.
쉽게 말해서, 특정 영역에서 특정 엘리먼트가 ViewPort에 보이는지 아닌지 여부를 쉽게 확인 가능한 것이다.
예를 들어, infinity Scroll을 구현한다고 가정해보자.
과거에는 현재 Scroll.Y 값이 컨텐츠를 보여주고 있는 요소의 끝에 닿았는지를 체크하는 것을 onScroll 이벤트에서 실행시켰을 것이다.
이것은 엄연히 Main Thread 내에서 동작하는 것이다. 비동기 처리 로직이 아니다.
그러나 Intersection Observer API를 이용하면, 특정 DOM 요소가 viewPort에 들어왔는지를 비동기 처리로 체크하여 Main Thread와 별개로 동작시킬 수 있다.
ViewPort에 들어온 순간, Observer로 적용시켰던 Callback 함수가 호출되므로 이후 동작을 처리할 수 있다.Send Beacon
서버로 Post 요청을 보낼 때, 안전하게 비동기로 처리할 수 있도록 해주는 API이다.
예를 들어,로그 수집과 같이 적은 데이터를 보내고 요청에 대한 응답이 중요하지 않을 때 사용할 수 있다.
Post 요청 자체가 비동기 처리여서 어떤 차이인지 의문이 들 수도 있으므로, 아래와 같은 예시를 봐보도록 하자.
특정 Iframe이 들어가 있는 Modal 창이 있고, 이 Modal을 종료하는 로그를 수집하려고 한다.
Modal을 닫으면 그대로 Iframe이 사라지면서, 로그를 수집하려던 Post 요청은 해당 Main Thread의 브라우저 자체가 사라지면서 Task를 잃게 된다. 그리고 로그 수집에 대한 Post 요청은 정상적으로 수행하지 못하게 된다.
(IFrame과 IFrame을 호출한 Parent는 서로 다른 Thread이기 때문이다.)이를 해결하기 위해, Post 요청에 대한 응답을 기다린 뒤 Modal을 닫게되면, 그만큼 유저는 불필요한 Post 요청에 대한 응답을 받을 때까지 무조건 기다려야 한다.
이러한 점을 Send Beacon을 이용하게 되면, 호출한 Main Thread 주최가 사라지더라도 비동기 처리 동작을 보장한다.
그만큼 서버 요청에 대한 응답이 중요하지 않은 Post 요청은 Send Beacon으로 처리하면 더욱 안전하다.
FireBase나 Google Analitics 등의 모듈에는 이러한 Send Beacon을 기반으로 로그를 수집하고 있다.이 외에도 비동기 처리를 활용한 많은 API가 있을 것으로 예상된다. 더욱 더 열심히 MDN은 정독해야겠다.
그리고 언젠간 자바스크립트가 Multi Thread 못지않게 더욱 발전할 수 있을 것이라 기대하고 있다. 🙏
반응형'개발 블로깅 > Javascript 개념' 카테고리의 다른 글
[2021.06.13] Javascript ECMA 2021 소개 (0) 2021.06.13 자바스크립트 메모리 관리(Garbage Collection)에 대해 알아보자 (0) 2020.09.04 Javascript Engine & Event Loop 동작 원리 (0) 2020.08.30 [2020.07.11] 자바스크립트 - Element.style 속성과 getComputedStyle() 메소드의 차이에 대해 알아보자 (3) 2020.07.12 [2019.04.23] 자바스크립트 event Loop 개념 (0) 2019.04.23