-
자바스크립트와 이벤트 루프Java Script/기본 개념 정리 2021. 1. 14. 01:49반응형
자바스크립트와 이벤트 루프
위키에 따르면, 이벤트 루프에 대해 아래와 같이 설명하고 있다.
컴퓨터 과학에서 이벤트 루프는 프로그램의 이벤트나 메시지를 대기하다가 디스패치(효율적으로 처리)하는 프로그래밍 구조체
일반적으로 이벤트가 도착할 때까지 요청을 차단하는 일부 내부 또는 외부의 이벤트 제공자에게 요청한 다음 관련
이벤트 핸들러를 호출(이 때 이벤트를 디스패치)
이벤트 제공자가 선택 또는 polling되는(유닉스 시스템 호출에서 실제로 폴링되지는 않음)
파일 인터페이스를 따르는 경우 이벤트 루프는 반응자와 결합해서 사용할 수 있다.
이벤트 루프는 거의 무조건 메시지 제공자와 비동기식으로 동작-
반응자
-
동시에 들어오는 서비스 처리 요청을 관리하는 이벤트 처리 패턴
-
이벤트 루프에 대해 어느정도 감이 잡혔으니, 자바스크립트에서 어떻게 이용되는지 살펴보자
-
자바스크립트는 어떻게 동시성(Concurrency)를 지원하는가?
-
자바스크립트는 단일 스레드 기반의 언어이다.
-
자바스크립트의 메인 스레드가 싱글 스레드이며, 단일 호출 스택을 사용한다.
-
-
스레드가 하나라는 말은 동시에 하나의 작업만 처리 가능하다는 의미
-
이 때, 이벤트 루프를 이용해서 동시성을 지원한다.
-
Node.js의 소개에는
이벤트 루프 기반의 비동기 방식으로 Non-Blocking IO를 지원..
이라는 문구가 적혀있다.
-
-
ECMAScript에는 이벤트 루프가 없다
-
실제로 V8과 같은 자바스크립트 엔진은 단일 호출 스택(Call Stack)을 사용하며, 요청이 들어올 때마다 해당 요청을 순차적으로 호출 스택에 담아 처리할 뿐
-
그렇다면 비동기 요청은 어떻게 이루어지며, 동시성에 대한 처리는 누가 하는가?
-
바로 자바스크립트 엔진을 구동하는 환경, 즉 브라우저나 Node.js가 담당
-
-
브라우저 환경
-
Node.js 환경
-
Node.js는 비동기 IO를 지원하기 위해 libuv 라이브러리를 사용하며, 이 libuv가 이벤트 루프를 제공
-
자바스크립트 엔진은 비동기 작업을 위해 Node.js의 API를 호출하며, 이때 넘겨진 콜백은 libuv의 이벤트 루프를 통해 스케쥴되고 실행
-
-
-
비동기에 대한 처리는 stack에 적재됐다가, Web API 영역으로 이동되어 실행 ~> 작업이 수행 또는 완료되면 task queue에 적재됨, main 실행이 끝난 후, event loop에 의해 task queue에 있는 완료된 작업들이 stack으로 이동되어 결과를 실행해준다.
-
동기화를 시킬 때는 콜백함수 또는 Promise 객체나 Async/Await을 활용해서 순차적으로 실행될 수 있게 함(위의 task queue로 움직이는 과정을 없애주는 것 같음)
-
Promise와 Async/Await을 사용하면서 흔히 말하는 콜백 지옥을 방지할 수 있음
-
-
비동기 API와 try-catch
setTimeout
뿐만 아니라 브라우저의 다른 비동기 함수들(addEventListener
,XMLHttpRequest
… )이나 Node.js의 IO 관련 함수들 등 모든 비동기 방식의 API들은 이벤트 루프를 통해 콜백 함수를 실행 -
그러면 아래와 같은 코드가 왜 에러를 잡아낼 수 없는지 살펴보자.
$('.btn').click(() => { // (A) try { $.getJSON('/api/members', res => { // (B) // 에러 발생 코드 }); } catch (e) { console.log('Error : ' + e.message); } });
-
위의 코드에서 버튼이 클릭되어 콜백 A가 실행될 때
$.getJSON
함수는 브라우저의XMLHttpRequest
API를 통해 서버로 비동기 요청을 보낸다. -
이후 실행을 마치고 호출 스택에서 제거된다.
-
이후 서버에서 응답받은 브라우저는 콜백 B를 태스크 큐에 추가하고 B는 이벤트 루프에 의해 실행되어 호출 스택에 추가된다.
-
하지만 이때 A는 이미 호출 스택에서 비워진 상태이기 때문에 호출 스택에는 B만 존재한다. 즉 B는 A가 실행될 때와는 전혀 다른 독립적인 컨텍스트에서 실행되며, A 내부의 try-catch 문에 영향을 받지 않는다.
-
이런 이유로 Node.js의 비동기 API들은 중첩된 콜백 호출에 대한 에러 처리를 위해 '첫 번째 인수는 에러 콜백 함수' 라는 컨벤션을 따르고 있다
-
-
이를 해결하기 위해서는 콜백 B의 내부에서 try-catch를 실행해야 한다. (물론, 이렇게 해도 네트워크 에러나 서버 에러는 잡을 수 없다. 이를 위해서는 에러 콜백을 따로 제공해야 한다.)
$('.btn').click(() => { // (A) $.getJSON('/api/members', res => { // (B) try { // 에러 발생 코드 } catch (e) { console.log('Error : ' + e.message); } }); });
-
-
Promise와 Event Loop
setTimeout(() => { // (A) console.log('A'); }, 0); Promise.resolve().then(() => { // (B) console.log('B'); }).then(() => { // (C) console.log('C'); });
-
위의 경우 콘솔에 찍히는 순서는 B -> C -> A 가 된다.
-
Promise는 마이크로 태스크(Micro Task)를 사용하기 때문이다.
-
마이크로 태스크(Micro Task)
-
쉽게 말해 일반 태스크보다 더 높은 우선순위를 갖는 태스크
-
태스크 큐에 대기중인 태스크가 있더라도 마이크로 태스크가 먼저 실행
-
The microtask queue is not a task queue.
-
-
-
-
setTimeout()
함수는 콜백 A를 태스크 큐에 추가하고, 프라미스의then()
메소드는 콜백 B를 태스크 큐가 아닌 별도의 마이크로 태스크 큐에 추가 -
위의 코드 실행이 끝나면 태스크 이벤트 루프는 (일반)태스크 큐 대신 마이크로 태스크 큐가 비었는지 먼저 확인하고, 큐에 있는 콜백 B를 실행
-
콜백 B가 실행되고 나면 두번째
then()
메소드가 콜백 C를 마이크로 태스크 큐에 추가 -
이벤트 루프는 다시 마이크로 태스크를 확인하고, 큐에 있는 콜백 C를 실행
-
이후에 마이크로 태스크 큐가 비었음을 확인한 다음 (일반) 태스크 큐에서 콜백 A를 꺼내와 실행
-
-
promise는 ECMAScript에 정의되어 있지만, 마이크로 태스크는 HTML 스펙이 정의되어 있는데, 둘의 연관관계가 명확하지 않았다.
-
ES6부터 프라미스를 위해 잡 큐(Job Queue)라는 항목을 추가했지만, HTML 스펙의 마이크로 태크스와는 별도의 개념
-
최근에 Living Standard 상태인 HTML 스펙을 보면 자바스크립트의 잡큐를 어떻게 이벤트 루프와 연동하는지에 대한 설명이 나와있다.
-
이 부분은 추가적인 정리가 필요할 것 같다. 대략적인 느낌은 이해했지만, 아직 정리할만큼 이해가 되지는 않은 것 같다.
-
-
Reference
https://ko.wikipedia.org/wiki/%EC%9D%B4%EB%B2%A4%ED%8A%B8_%EB%A3%A8%ED%94%84
https://meetup.toast.com/posts/89
반응형'Java Script > 기본 개념 정리' 카테고리의 다른 글
Async & Await , Promise (0) 2020.08.17 함수형 프로그래밍 (0) 2020.08.08 클로저(Closer) (0) 2020.08.08 Object와 Instance (0) 2020.08.08 Javascript Class(클래스), Prototype(프로토타입), Property(프로퍼티) (0) 2020.08.08 -