1. Event Loop ๐Ÿ‘ฉโ€๐Ÿ’ป

ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ ์Šค๋ ˆ๋“œ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ๋น„๋™๊ธฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” API ๊ฐ€ ์กด์žฌํ•œ๋‹ค. Swift ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” GCD, Run Loop ์™€ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์ด๋Ÿฐ ์—ญํ• ์„ ํ•˜๋Š”๋ฐ, ์ด ๊ฒฝ์šฐ ๋ฉ€ํ‹ฐ์ฝ”์–ด ํ™˜๊ฒฝ์—์„œ ์Šค๋ ˆ๋“œ ์ž์›์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ , ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.

ํ•˜์ง€๋งŒ JavaScript ์˜ ์ด๋ฒคํŠธ ๋ฃจํ”„๋Š” ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค. ๋น„๋™๊ธฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•จ์€ ๋งž์ง€๋งŒ, ๊ฐ€์žฅ ๊ฒฐ์ •์ ์ธ ์ด์œ ๋Š” ์‹ฑ๊ธ€์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์œผ๋กœ ์ธํ•œ ์ฝ”๋“œ ๋ธ”๋กœํ‚น์„ ๋ง‰๊ธฐ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๋‹ค. JavaScript ์—์„œ๋Š” ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ์—†์œผ๋ฉด ์ฝ”๋“œ์˜ ์‹คํ–‰์ด ๋ฉˆ์ถฐ๋ฒ„๋ฆฐ๋‹ค!

Event Loop

๊ทธ๋ฆฌ๊ณ  ์ด ์ด๋ฒคํŠธ ๋ฃจํ”„์—์„œ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ๋ฅผ ๋ณด๊ด€ํ•˜๋Š” Task Queue ๋Š” ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ผ Macrotask Queue ์™€ Microtask Queue ๋กœ ๋‚˜๋‰œ๋‹ค.

Task Queue

์œ„ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ์ž‘์—…์ด ์‹คํ–‰๋˜๋Š” ์ˆœ์„œ๋Š”

  1. Synchronous Task
  2. Asynchronous Task
    1. Microtask Queue: process.nextTick(), Promise callback, async functions, queueMicrotask()
    2. MacroTask Queue: setTimeout(), setInterval(), addEventListener()

์™€ ๊ฐ™๋‹ค.


2. Macrotask Queue ๐Ÿ‘ฉโ€๐Ÿ’ป

Macrotask Queue ๋˜๋Š” ๊ทธ๋ƒฅ Task Queue ๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ์ด๊ฒƒ์€ setTimeout(), setInterval(), addEventListener() ์™€ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์ถ”๊ฐ€๋œ๋‹ค.

function getData() {
  fetch('https://jsonplaceholder.typicode.com/todos/1')
    .then((response) => response.json())
    .then(({ title }) => console.log(`Task 2: ${title}`));
}

function handleHeavyTask(bFetch) {
  if (bFetch) getData();
  else console.log('Task 2: ์ง€์—ฐ ์—†์ด ์‹คํ–‰!');
}

(function () {
  console.log('Task 1');
  handleHeavyTask(false);
  console.log('Task 3');
})();
Task 1
Task 2: ์ง€์—ฐ ์—†์ด ์‹คํ–‰!
Task 3

Task 2 ๊ฐ€ Synchronous Task ์ด๋ฏ€๋กœ Task 1 > Task 2 > Task 3 ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋œ๋‹ค.

์ด๋ฒˆ์—๋Š” handleHeavyTask ์˜ arguments ๋กœ true๋ฅผ ๋„ฃ์–ด๋ณด์ž.

(function () {
  console.log('Task 1');
  handleHeavyTask(true);
  console.log('Task 3');
})();
Task 1
Task 3
Task 2: delectus aut autem

Task 2 ๊ฐ€ Asynchronous Task ์ด๋ฏ€๋กœ Task 1 > Task 3 > Task 2 ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋˜์—ˆ๋‹ค. Task 2 ๊ฐ€ Task Queue ์— ์Œ“์—ฌ ๋Œ€๊ธฐ๋˜๊ณ  ์žˆ๋‹ค ์‘๋‹ต์ด ์˜ค๊ณ  stack ์ด ๋นŒ ๋•Œ๋ฅผ ๊ธฐ๋‹ค๋ ธ๋‹ค ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.


์œ„์™€ ๊ฐ™์ด ๋™๊ธฐ ์ฝ”๋“œ์™€ ๋น„๋™๊ธฐ ์ฝ”๋“œ๊ฐ€ ์„ž์ด๋ฉด ์ˆœ์„œ๋ฅผ ์˜ˆ์ธกํ•˜๊ธฐ ํž˜๋“  ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ผ์ผํžˆ ํ•จ์ˆ˜๋ฅผ ๋”ฐ๋ผ๊ฐ€์„œ ์ „์ฒด ์ฝ”๋“œ๋ฅผ ๋ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ๊ฐ€์žฅ ์‰ฝ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ handleHeavyTask() ํ•จ์ˆ˜๊ฐ€ ํ•ญ์ƒ ๋น„๋™๊ธฐ๋กœ ์ž‘๋™ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

function handleHeavyTask(bFetch) {
  if (bFetch) getData();
  else setTimeout(() => console.log('Task 2: ์ง€์—ฐ ์—†์ด ์‹คํ–‰!'));
}

์ด์ œ arguments ๋กœ false๋ฅผ ์ž…๋ ฅํ•ด๋„ ์‹คํ–‰ ์ˆœ์„œ๊ฐ€ Task 1 > Task 3 > Task 2 ๋กœ ๋ณด์žฅ๋œ๋‹ค.

Task 1
Task 3
Task 2: ์ง€์—ฐ ์—†์ด ์‹คํ–‰!

3. Microtask Queue ๐Ÿ‘ฉโ€๐Ÿ’ป

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋น„๋™๊ธฐ ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•  ๋•Œ ์šฐ์„ ์ˆœ์œ„๋Š” ์–ด๋–ป๊ฒŒ ๋ ๊นŒ? ์šฐ์„ , ๋น„๋™๊ธฐ ์ฝœ๋ฐฑ์ด Queue ์— ๋“ค์–ด๊ฐ€๋Š” ์ˆœ์„œ์™€ stack ์ด ๋น„์›Œ์ ธ ์žˆ๋Š”์ง€๊ฐ€ ์ค‘์š”ํ•˜๋‹ค.

console.log('Task 1');
setTimeout(() => console.log('Task 2'));
console.log('Task 3');
setTimeout(() => console.log('Task 4'));
console.log('Task 5');

์œ„ ์ฝ”๋“œ๋Š” ๋™๊ธฐ ์ฝ”๋“œ Task 1, Task 3, Task 5 ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ๋น„๋™๊ธฐ ์ฝ”๋“œ Task 2 ์™€ Task 4 ๋Š” ์ฆ‰๊ฐ ์ฝœ๋ฐฑ์„ Queue ์— ๋Œ€๊ธฐ์‹œํ‚จ๋‹ค. ๋”ฐ๋ผ์„œ ์‹คํ–‰ ์ˆœ์„œ๋Š” Task 1 > Task 3 > Task 5 > Task 2 > Task 4 ๊ฐ€ ๋œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ Task 2 ๊ฐ€ Task 4 ๋ณด๋‹ค ๋” ๋ฌด๊ฒ๋‹ค๊ณ  ํ•ด๋ณด์ž.

console.log('Task 1');
setTimeout(() => console.log('Task 2'), 2);
console.log('Task 3');
setTimeout(() => console.log('Task 4'), 1);
console.log('Task 5');

๊ทธ๋Ÿฌ๋ฉด ๋™๊ธฐ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ๋น„๋™๊ธฐ ์ฝ”๋“œ Task 4 ๊ฐ€ ๋จผ์ € Queue ์— ์ถ”๊ฐ€๋˜์–ด ๋Œ€๊ธฐํ•˜๊ณ , ์ดํ›„ Task 2 ๊ฐ€ Queue ์— ์ถ”๊ฐ€๋œ๋‹ค. ๋”ฐ๋ผ์„œ ์‹คํ–‰ ์ˆœ์„œ๋Š” Task 1 > Task 3 > Task 5 > Task 4 > Task 2 ๊ฐ€ ๋œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๋™๊ธฐ ์ฝ”๋“œ๊ฐ€ ๋ฌด๊ฑฐ์›Œ stack ์„ ์˜ค๋žœ ์‹œ๊ฐ„ ์ ์œ ํ•ด Task Queue ์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋น„๋™๊ธฐ ์ฝ”๋“œ๊ฐ€ ์Œ“์—ฌ ์‹คํ–‰์„ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๋‹ค๊ณ  ํ•ด๋ณด์ž.

console.log('Task 1');
setTimeout(() => console.log('Task 2'));
console.log('Task 3');
setTimeout(() => console.log('Task 4 important!'));
console.log('Task 5');
Task 1
Task 3
Task 5
Task 2
Task 4 important!

Task 2 ์™€ Task 4 ๊ฐ€ ๋น„๋™๊ธฐ ์ฝ”๋“œ์ธ๋ฐ Task 4 ๊ฐ€ ๋” ์ค‘์š”ํ•ด ๋จผ์ € ์‹คํ–‰๋˜๊ธฐ๋ฅผ ์›ํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ? Task 2 ๋ณด๋‹ค Task 4 ๊ฐ€ Task Queue ์— ๋จผ์ € ์ถ”๊ฐ€๋˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค. ํ•˜์ง€๋งŒ ์œ„ ๊ฒฝ์šฐ๋Š” ๋น„๋™๊ธฐ ์ฝœ๋ฐฑ์ด ์ฆ‰์‹œ Queue ์— ์ถ”๊ฐ€๋˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์ธก์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์‹ค์ œ ์ฝ”๋“œ์—์„œ๋Š” ์˜ˆ์ธก์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•œ๋‹ค.


์ด๋ฅผ ์œ„ํ•ด์„œ Task Queue ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋น„๋™๊ธฐ ์ฝœ๋ฐฑ์ด ์Œ“์˜€์„ ๋•Œ ๋น„๊ต์  ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์ฝœ๋ฐฑ๊ณผ ๋‚ฎ์€ ์ฝœ๋ฐฑ์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด Task Queue ๋ฅผ 2๊ฐœ๋กœ ๋‚˜๋ˆ„์–ด ๊ด€๋ฆฌํ•œ๋‹ค. ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์ด๊ฒƒ์„ Microtask Queue ๋ผ ๋ถˆ๋ฆฌ๋Š” ์ด๊ฒƒ์€ process.nextTick(), Promise callback, async functions, queueMicrotask() ์™€ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์ถ”๊ฐ€๋œ๋‹ค.

console.log('Task 1');
setTimeout(() => console.log('Task 2'));
console.log('Task 3');
queueMicrotask(() => console.log('Task 4 important!'));
console.log('Task 5');
console.log('Task 1');
setTimeout(() => console.log('Task 2'));
console.log('Task 3');
Promise.resolve().then(() => console.log('Task 4 important!'));
console.log('Task 5');
Task 1
Task 3
Task 5
Task 4 important!
Task 2

Task Queue ์— Task 2 ๊ฐ€ ๋จผ์ € ๋“ฑ๋ก๋˜์—ˆ์ง€๋งŒ Task 4 ๊ฐ€ Microtask Queue ๋กœ ๋“ฑ๋ก๋˜์–ด ์‹คํ–‰ ์ˆœ์„œ๋Š” Task 1 > Task 3 > Task 5 > Task 4 > Task 2 ๊ฐ€ ๋œ๋‹ค.




Reference

  1. โ€œUsing microtasks in JavaScript with queueMicrotask().โ€ MDN Web Docs. Apr. 06, 2024, accessed Apr. 27, 2024, MDN - microtasks.
  2. โ€œ๋‘ ๊ฐœ์˜ queue ๋กœ ๋™์‹œ์„ฑ ์ œ์–ด.โ€ Youtube. Sep. 02, 2023, ๋‘ ๊ฐœ์˜ queue ๋กœ ๋™์‹œ์„ฑ ์ œ์–ด.