What is the Event Loop in Javascript - Lee-hyuna/33-js-concepts-kr GitHub Wiki

setTimeout(function doSomething() {
  // Do stuff.
}, 0);

0λ°€λ¦¬μ΄ˆλ₯Ό κΈ°λ‹€λ¦¬λŠ” μ΄μœ λŠ” λ¬΄μ—‡μž…λ‹ˆκΉŒ? κ·Έλƒ₯ 직접 doSomething을 callν•˜λŠ” 게 μ–΄λ•Œμš”?

정닡은 이 setTimeout이 Javascript 엔진에 0ms 이후 μž‘μ—…μ„ μ‹€ν–‰ν•˜λ„λ‘ μ§€μ‹œν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.

였히렀, Javascript 엔진에 μ΅œμ†Œ 0ms 후에 doSomething을 μ‹€ν–‰ν•˜λΌκ³  μ§€μ‹œν•©λ‹ˆλ‹€.

이것은 Javascript의 μž‘λ™ 방식을 μ΄ν•΄ν•˜κΈ° μœ„ν•œ 핡심 κ΅¬λΆ„μž…λ‹ˆλ‹€. doSomething은 거의 μ¦‰μ‹œ μ‹€ν–‰λ˜κ±°λ‚˜ λͺ‡ 초 후에 싀행될 수 μžˆμŠ΅λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄, λ‹€μŒ μ½”λ“œλ₯Ό κ³ λ €ν•©λ‹ˆλ‹€.

let startTime = Date.now();
setTimeout(function() {
  console.log(Date.now() - startTime);
}, 500);

while (Date.now() - startTime < 1000) {} // Pause for 1 second.

Javascript’s stack frames

ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λ©΄ "stack frame"이 μƒμ„±λ©λ‹ˆλ‹€. stack frame은 ν•¨μˆ˜μ— μ„ μ–Έλœ λͺ¨λ“  λ³€μˆ˜λ₯Ό μ €μž₯ν•˜λŠ” μ»¨ν…Œμ΄λ„ˆμž…λ‹ˆλ‹€.

κΈ°λŠ₯ 싀행이 μ™„λ£Œλ˜λ©΄ ν•΄λ‹Ή stack frame이 μ‚­μ œλ©λ‹ˆλ‹€. κΈ°λŠ₯ λ‚΄μ—μ„œ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λ©΄ 2개의 stack frame이 μƒμ„±λ©λ‹ˆλ‹€. 이것듀은 μŠ€νƒμ— λ†“μž…λ‹ˆλ‹€.

첫 번째 ν•¨μˆ˜μ— μ˜ν•΄ μƒμ„±λœ stack frame을 "initial frame"이라고 ν•©λ‹ˆλ‹€. 이 κΈ°λŠ₯은 λ‹€λ₯Έ κΈ°λŠ₯을 μž¬κ·€μ μœΌλ‘œ ν˜ΈμΆœν•  수 있으며 μ²˜μŒμ— ν•˜λ‚˜μ˜ κΈ°λŠ₯만 ν˜ΈμΆœν•œ ν›„ 수백만 개의 κΈ°λŠ₯이 호좜될 수 μžˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ“  κΈ°λŠ₯ 싀행이 μ™„λ£Œλ˜λ©΄ 초기 ν”„λ ˆμž„μ΄ μŠ€νƒμ—μ„œ μ œκ±°λ©λ‹ˆλ‹€. ν˜„μž¬ μ‹€ν–‰ 쀑인 κΈ°λŠ₯(i.e. none of your code)이 μ—†μŠ΅λ‹ˆλ‹€.

Javascript’s message queue

λͺ¨λ“  κΈ°λŠ₯이 μ‹€ν–‰λœ ν›„ μƒˆ κΈ°λŠ₯을 μ–΄λ–»κ²Œ ν˜ΈμΆœν•©λ‹ˆκΉŒ?

λ©”μ‹œμ§€ 큐에 μΆ”κ°€ν•˜κΈ°λ§Œ ν•˜λ©΄ λ©λ‹ˆλ‹€! Javascriptμ—λŠ” "λ©”μ‹œμ§€"의 λŒ€κΈ°μ—΄μ΄ μžˆμŠ΅λ‹ˆλ‹€.

각 λ©”μ‹œμ§€μ—λŠ” 단일 κΈ°λŠ₯이 ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μŠ€νƒμ΄ λΉ„μ–΄ 있으면(즉, λͺ¨λ“  κΈ°λŠ₯이 μ‹€ν–‰λ˜κ³  λ‚˜λ©΄) Javascript 엔진은 λ©”μ‹œμ§€ 큐가 λΉ„μ–΄ μžˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.

λΉ„μ–΄ μžˆμ§€ μ•ŠμœΌλ©΄ 엔진이 첫 번째 λ©”μ‹œμ§€λ₯Ό μ œκ±°ν•˜κ³  λ‚΄λΆ€ κΈ°λŠ₯을 μ‹€ν–‰ν•©λ‹ˆλ‹€.

κΈ°λŠ₯이 μ‹€ν–‰λ˜λ©΄ μƒˆ μŠ€νƒ ν”„λ ˆμž„(초기 ν”„λ ˆμž„)이 μƒμ„±λ˜μ–΄ μŠ€νƒμ— μΆ”κ°€λ©λ‹ˆλ‹€.

κΈ°λŠ₯ 싀행이 μ™„λ£Œλ˜λ©΄ 초기 ν”„λ ˆμž„μ΄ μŠ€νƒμ—μ„œ 제거되고, Javascript 엔진은 λ©”μ‹œμ§€ 큐에 λ‹€λ₯Έ λ©”μ‹œμ§€κ°€ μžˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€. λ©”μ‹œμ§€ λŒ€κΈ°μ—΄μ΄ λΉ„μ–΄ μžˆμ„ λ•ŒκΉŒμ§€ 이 μž‘μ—…μ΄ λ°˜λ³΅λ©λ‹ˆλ‹€.

Adding to the message queue

이제 Javascriptμ—λŠ” "λ©”μ‹œμ§€"κ°€ 쀄을 μž‡κ³  μžˆμŠ΅λ‹ˆλ‹€.

JavascriptλŠ” λ©”μ‹œμ§€ 큐가 λΉ„μ–΄ μžˆμ„ λ•ŒκΉŒμ§€ 각 λ©”μ‹œμ§€μ˜ κΈ°λŠ₯을 μ‹€ν–‰ν•©λ‹ˆλ‹€. μ• μ΄ˆμ— μ–΄λ–»κ²Œ λ©”μ‹œμ§€κ°€ λ©”μ‹œμ§€ 큐에 μžˆκ²Œλ˜λ‚˜μš”?

λ©”μ‹œμ§€ 큐에 μΆ”κ°€ν•˜λŠ” κ°€μž₯ 일반적인 방법은 이벀트(DOM μš”μ†Œ, XMLHttpRequest, μ„œλ²„ 보낸 이벀트 λ“±), setTimeout 및 setIntervalμž…λ‹ˆλ‹€.

지연 μ‹œκ°„μ΄ 500ms인 setTimeout을 μ‹€ν–‰ν•˜λ©΄ Javascript 엔진에 500ms 내에 λ©”μ‹œμ§€ 큐에 콜백 κΈ°λŠ₯을 μΆ”κ°€ν•˜λΌλŠ” λ©”μ‹œμ§€κ°€ ν‘œμ‹œλ©λ‹ˆλ‹€.

μŠ€νƒμ— 이미 μ‹€ν–‰ 쀑인 κΈ°λŠ₯도 μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€. 콜백 ν•¨μˆ˜λŠ” μŠ€νƒμ΄ λΉ„μ–΄ μžˆμ„ λ•ŒκΉŒμ§€ κΈ°λ‹€λ €μ•Ό μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

이것이 ν•¨μˆ˜κ°€ μ΅œμ†Œ 500ms 후에 μ‹€ν–‰λ˜λŠ” μ΄μœ μž…λ‹ˆλ‹€. 1000ms λ™μ•ˆ μΌμ‹œ μ€‘μ§€ν•œ μœ„μ˜ μ˜ˆμ—μ„œ setTimeout μ½œλ°±μ€ μ‹€ν–‰λ˜κΈ° 전에 1000ms – 500ms = 500ms λ™μ•ˆ λ©”μ‹œμ§€ λŒ€κΈ°μ—΄μ— μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€. μ΄λ²€νŠΈλŠ” λ˜ν•œ μ¦‰μ‹œ μ‹€ν–‰λ˜λŠ” λŒ€μ‹  이벀트 λŒ€κΈ°μ—΄μ— μΆ”κ°€λ©λ‹ˆλ‹€.

let startTime = Date.now();
document.onclick = function() {
  console.log(Date.now() - startTime);
};

while (Date.now() - startTime < 1000) {} // Pause for 1 second.

루프가 μˆœν™˜ν•˜λŠ” λ™μ•ˆ μ›Ή νŽ˜μ΄μ§€λ₯Ό ν΄λ¦­ν•˜λ©΄ 클릭 μ²˜λ¦¬κΈ°κ°€ μ¦‰μ‹œ μ‹€ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λŒ€μ‹  λ©”μ‹œμ§€ λŒ€κΈ°μ—΄μ— μΆ”κ°€λ©λ‹ˆλ‹€.

루프가 μ™„λ£Œλ˜λ©΄ Javascript 엔진이 Click handlerλ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€. console.logλŠ” 1000 λ˜λŠ” κ·Έ 이상을 좜λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€.

이전 κΈ°λŠ₯을 μ’…λ£Œν•˜κ³  클릭 ν•Έλ“€λŸ¬λ₯Ό μ‹œμž‘ν•˜λŠ” 데 λͺ‡ λ°€λ¦¬μ΄ˆκ°€ 걸릴 수 있기 λ•Œλ¬Έμ— μ•½κ°„ 더 높을 수 μžˆμŠ΅λ‹ˆλ‹€.

The event loop

이벀트 λ£¨ν”„λŠ” λ©”μ‹œμ§€ 큐에 더 λ§Žμ€ λ©”μ‹œμ§€κ°€ μžˆλŠ”μ§€ ν™•μΈν•˜λŠ” 루프λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€. 높은 μˆ˜μ€€μ—μ„œ λ£¨ν”„λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

while (await messageQueue.nextMessage()) {
  let message = messageQueue.shift();
  message.run();
}

λ©”μ‹œμ§€ λŒ€κΈ°μ—΄μ— λ©”μ‹œμ§€κ°€ μžˆλŠ” 경우 λ©”μ‹œμ§€ λŒ€κΈ°μ—΄μ—μ„œ λ‹€μŒ λ©”μ‹œμ§€κ°€ 제거되고 κ΄€λ ¨ κΈ°λŠ₯이 μ‹€ν–‰λ©λ‹ˆλ‹€.

그렇지 μ•ŠμœΌλ©΄ λ©”μ‹œμ§€ λŒ€κΈ°μ—΄μ— μƒˆ λ©”μ‹œμ§€κ°€ 좔가될 λ•ŒκΉŒμ§€ κΈ°λ‹€λ¦½λ‹ˆλ‹€. 이벀트 λ£¨ν”„λŠ” Javascriptμ—μ„œ 비동기화λ₯Ό ν—ˆμš©ν•˜λŠ” κΈ°λ³Έ λͺ¨λΈμž…λ‹ˆλ‹€.

JavascriptλŠ” 단일 μŠ€λ ˆλ“œμ΄κΈ° λ•Œλ¬Έμ— Javascriptμ—μ„œ 이벀트 루프가 ν•„μš”ν•©λ‹ˆλ‹€.

닀쀑 μŠ€λ ˆλ“œ μ–Έμ–΄μ—μ„œ μ΄λ²€νŠΈκ°€ 트리거되면 ν•΄λ‹Ή 이벀트 ν•Έλ“€λŸ¬κ°€ μ¦‰μ‹œ 싀행될 수 μžˆμŠ΅λ‹ˆλ‹€.

이미 μ‹€ν–‰ 쀑인 κΈ°λŠ₯이 μžˆλŠ” 경우 이벀트 μ²˜λ¦¬κΈ°κ°€ μƒˆ μŠ€λ ˆλ“œμ—μ„œ 싀행될 수 μžˆμŠ΅λ‹ˆλ‹€.

μ›Ή μž‘μ—…μžλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” ν•œ Javascriptμ—μ„œλŠ” λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.

JavascriptλŠ” 단일 μŠ€λ ˆλ“œμ΄κΈ° λ•Œλ¬Έμ— 이벀트 ν•Έλ“€λŸ¬λ₯Ό μ‹€ν–‰ν•˜κΈ° 전에 이전 κΈ°λŠ₯이 싀행될 λ•ŒκΉŒμ§€ κΈ°λ‹€λ €μ•Ό ν•©λ‹ˆλ‹€.

μ—¬λŸ¬ 이벀트 ν•Έλ“€λŸ¬κ°€ 트리거된 경우 트리거된 μˆœμ„œλŒ€λ‘œ μ‹€ν–‰λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€. 이것이 Javascriptκ°€ λ©”μ‹œμ§€ 큐와 이벀트 루프가 ν•„μš”ν•œ μ΄μœ μž…λ‹ˆλ‹€.

이벀트 λ£¨ν”„λŠ” μ²˜μŒμ—λŠ” ν˜Όλž€μŠ€λŸ¬μšΈ 수 μžˆμŠ΅λ‹ˆλ‹€. μ–΄λ–»κ²Œ μž‘λ™ν•˜λŠ”μ§€ μ•Œμ•„λ‚΄κΈ° μœ„ν•΄ λͺ‡ 가지 μ½”λ“œλ₯Ό 가지고 λ…ΈλŠ” 것을 μΆ”μ²œν•©λ‹ˆλ‹€. λΆˆν–‰ν•˜κ²Œλ„ (μ œκ°€ μ•„λŠ” ν•œ) μ–΄λ–€ λΈŒλΌμš°μ €μ—μ„œλ„ λ©”μ‹œμ§€ 큐λ₯Ό λ³Ό 수 μ—†κΈ° λ•Œλ¬Έμ— 이벀트 루프가 μ–΄λ–»κ²Œ μž‘λ™ν•˜λŠ”μ§€ μœ μΆ”ν•΄μ•Ό ν•©λ‹ˆλ‹€.

λ‹€μŒμ€ 이벀트 루프λ₯Ό 보닀 잘 μ΄ν•΄ν•˜κΈ° μœ„ν•΄ μž¬μƒν•  수 μžˆλŠ” μ˜ˆμ œμž…λ‹ˆλ‹€.

document.addEventListener('click', () => {
  setTimeout(console.log.bind(console, 'third'), 0);
  setTimeout(console.log.bind(console, 'fifth'), 100);
  console.log('first');
});

document.addEventListener('click', () => {
  setTimeout(console.log.bind(console, 'fourth'), 0);
  console.log('second');
});