우선 자바스크립트의 동기적 처리와 비동기 처리에 대해서 알아봅시다.
자바스크립트는 기본적으로 단일 스레드(single-threaded) 언어로, 한 번에 하나의 작업만 처리할 수 있습니다. 이런 특성 때문에 자바스크립트에서는 동기(synchronous) 처리와 비동기(asynchronous) 처리라는 두 가지 방식으로 나뉘게 됩니다.
동기적 처리(Synchronous)
동기적 처리는 코드가 순차적으로 실행되며, 한 작업이 완료될 때까지 다음 작업은 대기 상태에 있습니다. 즉, 현재 실행 중인 작업이 완료되어야만 다음 작업을 시작할 수 있습니다.
비동기적 처리(Asynchronous)
비동기적 처리란 여러 작업을 동시에 처리할 수 있다는 것이 특징입니다. 특정 작업의 완료를 기다리지 않고 다른 작업을 동시에 수행할 수 있도록 합니다.
자바스크립트는 비동기 내장함수를 제공하는 데, 그것은 바로 setTimeout, XMLHttpRequest, fetch() 입니다.
setTimeout으로 자바스크립트에서 비동기 처리를 어떻게 하는지 간단하게 알아보겠습니다.
const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");
bar();
foo();
baz();
1.bar가 호출되면 setTimeout을 반환하고 콜스택에 추가 된다.setTimeout 함수는 비동기 함수이기 때문에 타이머가 완료 될 때까지 콜스택에 하염없이 머물지 않고, 미래에 실행될 것을 약속한 후에 setTimeout의 콜백 함수가 Web API에 추가되어 비동기적으로 실행 된다.
2.Web API에서 콜백함수의 타이머가 실행되는 동안 foo가 호출되어 콜스택에 추가된다. 실행 완료 후 콜스택을 빠져나가며 "First"를 기록한다.
3.baz가 호출되어 콜스택에 추가된다. 실행 완료 후 콜스택을 빠져나가며 "Third"를 기록한다.
4.foo와 baz가 실행되는 동안 콜백함수의 타이머가 완료되면 콜백함수는 Task Queue 에 들어가서 콜스택이 비워질 때까지 기다린다. 이 때, 이벤트 루프가 콜스택과 Task Queue의 상태를 확인하며 콜스택이 비워지면 콜백 함수를 콜스택으로 이동시킨다. 이렇게 해서 콜백함수의 실행이 완료되면 콜스택을 빠져나간다.
여기까지는 다들 알고 계시듯이 이벤트 루프의 기본을 설명하였습니다. 알아보다보니,
Task Queue가 두가지 종류로 나뉜다는 것을 알게 되었는데요. Event Loop는 브라우저에 존재하는 여러 Queue들에 우선순위를 부여해 어떤 task를 먼저 수행할지 결정하게 됩니다.아래에서 설명하도록 하겠습니다.
흔히 알고 있듯이 기존에 Task Queue는 Macrotask Queue 와 Microtask Queue 로 구분 됩니다.
이 Queue는 자주 사용하는 setTimeout(), setInterval() 와 같은 task를 넘겨 받습니다.
MicroStack Queue와 다르게 콜백 함수를 하나씩 실행합니다. 즉, 콜백 함수 하나를 실행하면 이벤트 루프를 놓아주어 다른 동작을 수행할 수 있도록 하게 됩니다.
MicroStack Queue에 등록 된 모든 콜백 함수 실행 이후마다, 즉 이벤트 루프의 한 주기에 여러 번 처리됩니다.
영상은 보시면 아래 순서로 실행됩니다.
1. Task1 이 콜스택에서 사라진 후
2. MicroStack Queue에 쌓여있는 Task 2, Task3, Task4를 모두 처리 후
3. MacroTask Queue에 쌓여있는 Task5, Task6을 하나씩 처리
❕Microtask의 우선순위는 일반 task(또는 macrotask)보다 더 높다는 것
코드로 자세한 과정을 아래에서 이해해보도록 하겠습니다.
// 1. 실행
console.log('script start')
// 2. task queue로 전달
setTimeout(function() {
// 8. task 실행
console.log('setTimeout')
}, 0)
// 3. microtask queue로 전달
Promise.resolve()
.then(function() {
// 5. microtask 실행
console.log('promise1')
// 6. microtask queue로 전달
})
.then(function() {
// 7. microtask 실행
console.log('promise2')
})
// 4. 실행
console.log('script end')
Promise로 비동기 호출을 하면 해당 작업은 Microtask Queue에 쌓이게 되는데, Microtask는 setTimeout과 같은 일반 Task보다 높은 우선순위를 가지고 있어서, Promise함수의 내용이 setTimeout함수의 내용보다 더 먼저 출력되는 것을 확인할 수 있습니다.
위 과정은 아래에서 상세하게 설명하도록 하겠습니다.
1.console.log('Start!')가 콜 스택(Call Stack)에서 실행되고, 실행이 완료되면 콜 스택에서 제거됩니다.
2.setTimeout() 함수가 콜 스택에서 실행됩니다. 이 때, setTimeout의 콜백 함수는 Web API로 전달됩니다.
3.설정된 대기 시간이 경과하면 이 콜백 함수는 매크로 태스크 큐(Macrotask Queue)로 이동합니다.
그 후에 Promise.resolve().then(res => console.log(res)) 구문이 실행됩니다. 여기서 Promise.resolve()는 Promise 객체를 반환하며 즉시 완료됩니다(즉, 'resolved' 상태입니다). 그래서 .then() 메서드에 등록된 콜백 함수는 마이크로 태스크 큐(Microtask Queue)로 전달되고, Promise.resolve() 자체는 콜 스택에서 제거됩니다.
4.다음으로 console.log('End!')가 콜 스택에서 실행되며, 그 후에 제거됩니다.
5.현재 매크로 태스크가 모두 처리된 상태입니다(즉, 현재의 JavaScript 코드 블록인 매크로 태스크가 종료된 상태입니다). 따라서 마이크로 태스크 큐에 있는 모든 작업들을 처리하기 시작합니다. 여기서 .then()의 결과인 console.log(res) 가 호출되어 출력하게 됩니다.
6.마지막으로 매크로 태스크 큐에 있는 작업들을 처리하기 시작합니다 - 여기서 setTimeout의 결과인 Timeout이 출력됩니다.
Async/Await이 Microtask Queue로 전달되는 과정을 확인해보도록 하겠습니다.
1.가장 먼저 console.log('Before function')이 콜 스택(Call Stack)에서 실행되고, 실행이 완료되면 콜 스택에서 제거됩니다.
2.다음으로 myFunc() 함수가 콜 스택에 넣어집니다. 그리고 함수 내부에 있는 console.log('In function!')이 콜 스택에서 실행되고 제거됩니다
3.그 후, const res = await one() 구문이 실행됩니다. 이 때, one() 함수가 콜 스택에 넣어지며, 이 함수는 Promise 객체를 반환하므로 즉시 완료된 상태입니다(Promise.resolve('One!')). 따라서 Promise 객체는 해결(resolved) 상태로 변경되며, one() 함수는 콜 스택에서 제거(pop)됩니다.
4.따라서 console.log('After function!') 구문이 먼저 콜 스택에 넣어져 실행되고 제거됩니다.
5.그 후 마이크로 태스크 큐에서 myFunc()의 남은 부분인 console.log(res)가 호출되어 'One!' 문자열을 출력합니다.
1.myFunc라는 함수에서 one()이라는 함수를 호출합니다.
2.one() 함수가 실행되면서 Promise 객체를 반환합니다.
3.Promise 객체가 반환된 순간, one()함수의 역할은 끝나므로 그 자리에서 종료됩니다. 이 때문에 one()함수는 콜 스택에서 제거됩니다.
4.그러나 반환된 Promise 객체가 '해결(resolved)' 상태로 바뀌기까지 일정 시간이 필요합니다. 이 '해결' 과정은 보통 비동기 작업(예: 네트워크 요청)에 의해 수행됩니다.
5.async/await 구문이 사용되면 해당 async 함수의 실행은 일시적으로 멈추고 Promise의 결과를 기다립니다 (즉, await 키워드에 도달).
6.해당 Promise가 해결되면, async 함수 내부에서 남은 코드들이 Microtask Queue로 추가되며, 현재 매크로 태스크가 완료된 후 실행됩니다.
따라서 비동기 작업을 수행하는 one()함수 자체의 실행은 빠르게 종료되어 콜 스택에서 제거되지만(즉시 Promise 반환), 그 결과인 Promise 객체가 '해결' 상태로 바뀔 때까지 기다려야 하는 것입니다.
그리고 await 키워드를 마주칠 때마다 해당 async 함수 내부의 나머지 부분들이 Microtask Queue에 추가되어 순차적으로 처리됩니다. 이렇게 함으로써 JavaScript는 비동기 로직을 동기식 방식처럼 관리할 수 있게 됩니다.
Animation Frames이란?
window.requestAnimationFrame() 메서드는 브라우저에 애니메이션을 실행할 것임을 알리고 다음 다시 그리기 직전에 브라우저가 지정된 함수를 호출하여 애니메이션을 업데이트하도록 요청합니다. 이 메서드는 다시 칠하기 전에 호출할 콜백을 인수로 받습니다.
간단하게 특징으로는
해당 내용은 https://www.youtube.com/watch?v=8aGhZQkoFbQ 이 영상을 시청하시면 좋을 거 같습니다.