자바스크립트에서 비동기를 제어하는 4가지 방법


자바스크립트에서 비동기를 제어하는 방법에는 총 "4가지"가 있다고 알려져 있다.

1) 콜백함수

2) 프로미스

3) async/await

4) 제너레이터

 

각각의 사용 방법 차이를 예제 코드와 함께 살펴보면 아래와 같다:

1) 콜백함수

function asyncFunction(callback) {
  setTimeout(() => {
    callback('Hello, world!');
  }, 1000);
}

asyncFunction((result) => {
  console.log(result);
});

2) 프로미스

function asyncFunction() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Hello, world!");
    }, 1000);
  });
}

asyncFunction()
  .then((result) => console.log(result))
  .catch((err) => console.log(err));

3) async/await

function asyncFunction() {
  return new Promise((resolve, resject) => {
    setTimeout(() => {
      resolve("Hello, world!");
    }, 1000);
  });
}

async function main() {
  try {
    const result = await asyncFunction();
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}

main();

4) 제너레이터

function* asyncFunction() {
  yield new Promise((resolve) => {
    setTimeout(() => {
      resolve('Hello, world!');
    }, 1000);
  });
}

const gen = asyncFunction();
const promise = gen.next().value;

promise
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });

 


문제 💡
아래와 같은 코드에서 콘솔에 찍히는 결과는 어떻게 나올까?


문제

console.log("start");

// 일반
console.log("1");

// Callback
setTimeout(() => {
  console.log("2");
}, 0);

// Promise
const promise = new Promise((resolve, reject) => {
  resolve("3");
});
promise.then((result) => console.log(result));

// async/await
const promise2 = new Promise((resolve) => {
  resolve("4");
});
async function test() {
  try {
    const res = await promise2;
    console.log(res);
  } catch (e) {
    console.log(e);
  }
}
test();

// Generator
function* generator() {
  yield "5";
}
const gen = generator();
console.log(gen.next().value);

console.log("end");

 

정답

start → 1(일반) → 5(제너레이터)→ end(일반) → 3(promise) → 4(async/await) → 2(callback)


해설 ✍️
이벤트 루프에 대해 알아보자

 


왜 정답이 이렇게 나왔는지 알려면 이벤트 루프(event loop)의 개념에 대해 알아야 한다.

이벤트 루프란 자바스크립트의 동시성을 관리하는 핵심 메커니즘이다. 

그 메커니즘을 간단하게 한 번, 자세히 한 번 이렇게 두 번으로 나누어 보며 이해해보자.

 

1) 자바스크립트 비동기 메소드의 브라우저 상 기본 동작 원리

자바스크립트에서 비동기 메소드는 기본적으로 아래와 같이 작동한다.

 

실행 콜스택(call stack)에서 이루어진다.

➡️ 시간이 오래 걸리는(비동기) 메소드의 경우 작업의 효율성을 위해 큐(queue)로 이동해 대기한다.

➡️ 다른 작업이 다 끝나면 다시 콜스택으로 호출 해 실행한다. 

https://blog.learncodeonline.in/javascript-event-loop,

 

 

2) 큐(queue)의 종류: 매크로 테스크 큐(Macrotask Queue)와 마이크로 테스크 큐(Microtask Queue)

좀 더 자세히 살펴보면, 사실 비동기 메소드들이 대기하는 큐(queue)는 작업 성질에 따라 여러 종류가 있다. 

마이크로 테스크 큐의 작업이 매크로 테스크 큐의 작업보다 앞서 처리되는 것을 확인할 수 있다.

 

대표적인 2가지는 매크로테스크 큐(Macrotask Queue, 일반적인 큐는 보통 이 매크로테스크 큐를 가리킨다) 마이크로테스크 큐(Microtask Queue)이다.

 

이러한 큐는 종류에 따라 실행 순서가 다르다.

예를 들어, 마이크로테스크 큐에 들어간 api는 매크로테스크 큐의 api보다 먼저 처리된다.

즉, 1️⃣ call stack ➡️ 2️⃣ Microtask Queue ➡️ 3️⃣ Macrotask Queue 순서로 처리된다.

 

api 종류에 따라 다른 큐에 배치되며 위에서 언급된 비동기 메소드들의 경우,

  • Promise, async/await ➡️ 마이크로 테스크 큐
  • setTimeout ➡️ 매크로 테스크 큐
  • Generator 의 경우, 비동기 기능을 가지지만 큐로 이동되지 않고 첫 콜 스택에서 바로 처리된다.

결론


위 내용을 요약하면 아래와 같다.

이벤트 루프란

큐에는 여러 종류가 있으며, 각 큐는 실행 순서가 있다.
마이크로 큐는 매크로 큐의 작업보다 우선적으로 실행된다.

자바스크립트에서 쓰이는 비동기 처리의 4가지 방법은 각각의 큐/스택을 거쳐 실행된다.
1) 콜백함수: call stack ➡️ Macrotask Queue ➡️ call stack 
2) 프로미스: call stack ➡️ Microtask Queue ➡️ call stack 
3) async/await: 프로미스 기반이므로 프로미스와 작동원리 같음.(즉, 마이크로 큐)
4) 제너레이터: call stack 에서 바로 처리. (비동기지만 Queue로 이동하지 않고 call stack에서 바로 처리됩니다.)

 

따라서 위 문제의 코드 동작 결과는 아래와 같다:

 1️⃣ call stack (start  1  5  end) ➡️ 2️⃣ Microtask Queue (3  4) ➡️ 3️⃣ Macrotask Queue (2)

 

 


Reference


 

1.

What the heck is the event loop | Philip Roberts | JSConf EU

 

 

2.

Microtasks and (Macro)tasks in Event Loop

https://medium.com/@saravanaeswari22/microtasks-and-macro-tasks-in-event-loop-7b408b2949e0

 

Microtasks and (Macro)tasks in Event Loop

JavaScript has a concurrency model based on an event loop, which is responsible for executing the code, collecting and processing events…

medium.com

 

 


Last Updated on 2024/07/02

 

 

 

+ Recent posts