자바스크립트가 싱글 스레드인 이유와, 엔진이 task를 처리하는 이벤트 루프의 기본 동작을 정리합니다.
자바스크립트의 비동기 동작을 이해하려면 먼저 자바스크립트가 왜 싱글 스레드로 동작하는지, 그리고 그 위에서 작업들이 어떤 순서로 처리되는지부터 짚고 넘어가야 합니다. 이 글에서는 그 출발점이 되는 이벤트 루프의 기본 동작을 정리합니다.
JS는 Single Thread에서 작동합니다. 즉 한 번에 하나의 작업만 동기 방식으로 처리할 수 있습니다.
동기(synchronous): 직렬 방식으로 작업을 처리하는 것. 요청이 시작하고 응답을 받은 후에야 다음 작업을 처리합니다.
비동기(Asynchronous): 병렬 방식으로 작업을 처리하는 것. 요청이 시작한 후 응답과 관계없이 다음 작업을 처리합니다.
사용자가 검색어를 입력해 검색을 위한 네트워크 요청이 발생한 순간에도 다른 작업을 처리할 수 있습니다. 이것이 비동기적 작업 방식입니다.
리액트에서는 16 버전에 접어들면서 비동기식으로 작동하는 방법이 소개되었습니다.
과거, 프로그램을 실행하는 단위가 오직 Process 뿐이었습니다.
Process
프로그램을 구동해 프로그램의 상태가 메모리 상에서 실행되는 작업 단위
현재는 하나의 프로그램에 여러 가지 작업이 필요해졌고, 더 작은 실행 단위인 thread가 등장했습니다. 하나의 process에는 여러 개의 thread를 만들 수 있고, thread끼리는 메모리를 공유하며 여러 작업을 동시에 수행할 수 있습니다.
자바스크립트는 기본적으로 싱글 스레드입니다.
비동기 동작과 뗄 수 없는 개념이 바로 Event Loop입니다.
Node.js와 마찬가지로 브라우저 측 자바스크립트 실행 흐름도 Event Loop에 기반을 둡니다. Event Loop를 잘 알고 있어야 올바른 아키텍처 설계가 가능합니다.
자바스크립트 엔진은 다음의 알고리즘을 갖고 있습니다.
자바스크립트 엔진은 대부분의 시간을 아무 일도 하지 않고 쉬다가, 스크립트나 핸들러, 이벤트가 활성화될 때만 돌아갑니다.
자바스크립트 엔진을 활성화하는 task는 다음과 같습니다.
<script src="...">가 로드될 때, 이 스크립트를 실행하는 것mousemove 이벤트와 이벤트 핸들러를 실행하는 것setTimeout에서 설정한 시간이 다 된 경우, 콜백 함수를 실행하는 것task는 하나의 집합을 이룹니다. 자바스크립트 엔진은 해당 task를 차례대로 처리하며 새로운 task가 추가될 때까지 기다립니다. task를 기다리는 동안 CPU 자원 소비는 0에 가까워지고 엔진은 잠들게 됩니다.
엔진이 바쁠 때 task가 추가되면 해당 task는 Macrotask queue에 추가됩니다.
참고로
task queue의 구조는queue가 아닌set입니다. 선택된 큐에서 실행 가능한 가장 오래된 task를 가져와야 하기 때문입니다. 무조건 먼저 들어온 첫 번째 항목을dequeue하는 것이 아니라, 실행 가능한 것들 중 가장 오래된 task를 차례대로 실행시킵니다.https://html.spec.whatwg.org/multipage/webappapis.html#definitions-3
(script를 처리하는 도중 mousemove 핸들러도 실행되고, setTimeout에서 설정한 시간이 지나 콜백이 들어온 경우의 상태를 그림으로 나타냈습니다.)
자바스크립트 엔진은 이들을 차례대로 처리합니다. 이때 알아야 할 두 가지가 있습니다.
바로 이 두 번째 특성 때문에 무거운 task 하나가 화면 전체를 멈춰 세울 수 있습니다. 다음 편에서는 이런 무거운 작업을 어떻게 쪼개서 화면을 살려두는지 살펴보겠습니다.
Promise 핸들러가 항상 비동기로 실행되는 이유인 마이크로태스크 큐를 짚고, 이를 편하게 다루는 async/await 문법과 실행 순서 종합 문제로 시리즈를 마무리합니다.
Promise.all/allSettled/race 같은 정적 메서드, AbortController로 요청을 취소하는 방법, 그리고 콜백을 Promise로 바꾸는 Promisification을 정리합니다.
then을 이어 붙여 비동기 작업을 순서대로 처리하는 Promise 체이닝과, fetch 예제, 그리고 체인에서 에러를 다루는 방법을 정리합니다.
특정 요청이 아니라 아무 요청에서나 502가 났습니다. 커넥션을 재사용하는 쪽과 받는 쪽의 타임아웃이 서로를 모른 채 돌고 있었고, 그 틈에서 죽어가는 소켓이 재사용되고 있었습니다.
메타 태그를 다 넣었는데도 검색 결과는 파란 링크 한 줄뿐이었습니다. 검색엔진이 페이지를 '읽는' 것과 '이해하는' 것은 다른 문제였고, 그 사이를 메우는 게 구조화된 데이터였습니다.
전역 Suspense로 감싸면 경고는 사라지지만, 페이지의 정적 마크업까지 placeholder로 대체됩니다. Next.js 소스코드를 따라가 BailoutToCSRError가 CLIENT_RENDERED 경계로 어떻게 흐르는지 살펴봅니다.
문서에는 "queued"라고만 적혀 있습니다. 호출 단위인지 컴포넌트 단위인지 탭 전체인지 알 수 없어서, 30개 요청을 세 가지 방식으로 직접 재봤습니다.
참조형 데이터는 내용이 같아도 주소가 다르면 다른 값으로 취급됩니다. 이 특성이 useEffect 의존성 배열과 React.memo에서 왜 문제가 되는지, useCallback과 useMemo가 무엇을 해결하는지, 그리고 왜 모든 곳에 쓰면 안 되는지 정리합니다.
인앱 브라우저에서 기능이 깨지는 문제와 대용량 목록 렌더링 성능 문제, 두 가지를 실제로 부딪히고 풀어낸 기록입니다.