JS Event Loop

GGomBee·2021년 7월 1일
0

JavaScript & TypeScript

목록 보기
3/4
post-thumbnail

Event Loop

단일 스레드

자바스크립트는 단일 스레드 기반의 언어로 한순간 하나의 작업만을 처리할 수 있다.

이는 동시에 아무것도 하지 못한다는 의미이며 콜스택이 하나라는 의미이기도 하다.

여기서 콜스택이란 무엇일까?

또한 비동기로 동작하기 때문에 단일 스레드에도 불구하고 많은 작업을 수행한다.

콜스택

콜스택은 data stucture로써 쉽게 말하면 코드가 실행되는 순서를 기억하는 부분이라고 생각하면 편하다.

비동기로 작동하는 자바스크립트의 특성상 콜스택에 쌓인 작업은 하나씩 실행되는 순서를 기억해 놓았다가 하나씩 실행한다.

비동기

아래 예제를 한번 보면 이해를 하기 쉬울 것이다.

function a(){
    console.log("astart")
    b()
    console.log("aend")
}

function b(){
    console.log("bstart")
    c()
    console.log("bend")
}

function c(){
    console.log("cend")
}

a()

이렇게 된 코드를 실행한다 했을때 아래와 같은 순서를 콜스택이 기억했다가 코드가 실행된다

a
a - b
a - b - c
a - b
a

콘솔을 찍어보면

이렇게 나오는 것을 확인할 수 있다.

이처럼 콜스택은 비동기 작업에 영향을 미치고 콜스택이 빈상태를 idle이라고 한다.

위와같은 순서로 작업되는 자바스크립트의 특성이 이벤트 큐 도입의 원인이 되었다.

Front End 활용

let queue = []

setInterval(()=>{
    console.log(queue)
}, 1000)

function enqueue (){
    queue.push(
        {Jobname:"hello"}
    )
}

enqueue()

위 코드를 실행하면 1초에 한번씩 콘솔이 찍히는것을 확인할 수 있다.

그런데 아래와 같이 조건을 걸어준다면 ,

let queue = []

setInterval(()=>{
    if(queue.length > 0) {
        console.log(queue[0].Jobname)
        queue.splice(0,1)
    }
}, 1000)

function enqueue (){
    queue.push(
        {Jobname:"hello"}
    )
}

enqueue()

이렇게 한번만 실행되는것을 확인할 수 있다.

우리는 이런 방식을 활용해서 프론트에서의 최적화를 진행할수있다.

window.onscroll(()=>{
a()
},1000)

function a() {
console.log('dddd')
}

위와같은 함수가 있다고 해보자.

함수의 실행시간이 짧을때는 문제가 없을수도 있겠지만 긴작업이 들어있으면 아래와 같이 이벤트큐가 계속 밀릴것이다.

onscroll—onscroll—onscroll—
a——a
a——a
a——a
a——a

하지만 최적화를 통해 큐가 밀리는 시간을 조금 단축할 수 있다.

1. 초당 함수의 실행 횟수를 제한하는 방법 ( lodash.throttle)

throttle은 이벤트를 반복적으로 실행할 때, 콜백 함수의 불필요한 실행을 줄여주는 역할을 한다.

불필요한 서버 리퀘스트를 막을 수도 있고, 불필요한 통신을 줄임과 동시에 필요없는 렌더링 또한 막을 수 있어 컴포넌트의 성능 개선에도 도움을 준다.

특히 외부 api를 사용할 경우 일일 할당량에 제한이 있는 경우가 있는데, 과도한 서버 요청을 막아줄 수 있다는 면에서 필수적으로 사용해야 할 기능이다.

일정주기, 매 밀리세컨드마다 최대 한 번만 호출될 수 있도록 throttle된 함수를 만든다.

비슷한 예로 debounce라는 개념이 있는데 debounce는 이벤트가 끝날때까지 기다렸다가 시작되는점, throttle은 이벤트가 시작되면 일정주기로 계속 실행한다는 점이 다르다.

따라서 확실한 성능 개선을 위해서는 debounce를 사용하여 이벤트를 한번만 실행되도록 하는 것이 효과적이다.

하지만 유저가 즉각적인 결과를 요하는 기능에 있어서는 throttle을 사용하는 것이 바람직하다.

즉, throttle은 너무 많은 이벤트 때문에 성능상의 문제가 있을때 사용하면 좋고, debounce는 마지막 이벤트만 의미가 있을 경우(ex.검색어 자동완성)에 사용하는것이 적합하다.

참고 :https://velog.io/@edie_ko/React-컴포넌트-성능-향상-시키기-feat.-Lodash-throttle-debounce

2. requestAnimationFrame (60Fps를 보장하는 API)

60Frame = 1ch 60fps = 16ms

16ms이 지나면 특정이벤트를 리턴하고 새로운 이벤트를 시작한다.

사용하는 방법은 소괄호안에 반복할 함수를 넣으면 된다.

window.onscroll(()=>{
    requestAnimationFrame(()=>{
        console.log("hello")
    })
})

이것을 사용하는 이유는 무엇일까?

  • 백그라운드 동작 및 비활성화시 중지(성능 최적화)
  • 최대 1ms(1/1000s)로 제한되며 1초에 60번 동작
  • 다수의 애니메이션에도 각각 타이머 값을 생성 및 참조하지 않고 내부의 동일한 타이머 참조

실제로 애니메이션을 구현해보면 그 차이를 알 수 있다.

const fire2() {
	setTimeout(() => {
		console.log(fire2)
	}, 0)
}

위와같이 setTimeout을 0ms뒤에 실행한다는 것은 지금실행시가 아닌 다음 컨텍스트때 콜스택을 실행한다는 의미를 갖는다.

Node.js (Batching 작업) or React

setState 예제

let nowState = {};
let started = false;
let called = false;

function setState(state){
    started=false;
    nowState = state;
    if(called){
        console.log(state);
        setTimeout(()=>{
            called = false;
            if(started){
                started = false;
                console.log(nowState);
            }
        },1000);
    }
    called = true;
};

setState({a:1});
setState({a:2});
setState({a:3});
setState({a:4});
setState({a:5});
setState({a:6});
setState({a:7});

위와같이 코드를 작성하고 함수 실행을 하면 순서대로 콘솔이 찍히는것을 확인할 수 있다.

setState (실행중 배치)

이를 실행중 async와 await를 이용해서 실행중 배치작업으로 만들 수 있다.

let nowState = {};
let started = false;
let called = false;

function setState(state){
    started=false;
    nowState = state;
    if(called){
        console.log(state);
        process.nextTick(()=>{
			called = false
			if(started){
				started = false
				console.log(nowState)
			}
		}, new TypeError(''))
    }
    called = true;
};

(async()=>{
    setState({a:1});
	setState({a:2});
	setState({a:3});
	setState({a:4});
	await setState({a:5});
	setState({a:6});
	await setState({a:7});
})();

DataLoader 를 사용한 Batch

const DataLoader = require('dataloader');
const something = new DataLoader(async (ids) => {
	console.log(ids)
	return ids
});
(async()=>{
	something.load(1);
	something.load(2);
	await something.load(3);
	something.load(5);
})();

자바스크립트 비동기 핵심 이벤트 루프

스크롤 이벤트 최적화

profile
Stay Hungry, Stay Foolish! 겸손한 개발자 고은비입니다. 언제나 성장하기 위해 노력하며 유의미한 데이터로 사용자의 경험을 향상시키는 방법에 관심이 많습니다. 성장하고 싶어요!! 피드백은 언제나 환영입니다!

0개의 댓글