자바스크립트의 경우 싱글 스레드 기반 언어이기 때문에 네트워크 관련 요청을 비동기적으로 처리하게 된다.
만약 동기적으로 처리한다면 즉시 처리되는 요청은 사용자에게 불편함을 주지 않으나,
어느 정도 시간이 걸리는 경우에는 클라이언트(브라우저)에서 네트워크 요청 처리하기 위해 다른 작업이 중단 될 것이다.
그래서 브라우저 에서는 이런 비동기 작업들을 처리하기 위한 장치들로 이벤트 루프와 태스크 큐 등을 가지고 있다
V8과 같은 자바스크립트 엔진은 단일 호출 스택(Call Stack)을 사용하며, 요청이 들어 올 때마다 해당 요청을 순차적으로 호출 스택에 담아 처리한다. 단일 호출 스택만을 사용한다면, pop을 통해 호출 스택에 있는 요청을 처리하기 때문에 비동기 요청 및 동시성에 대한 처리는 불가능해 진다. 이러한 문제는 자바스크립트 엔진 구동 환경인 브라우저나 Node.js의 내부에서 처리한다.
크롬 웹 브라우저는 V8 엔진이 탑제된 자바스크립트 런타임이다.
자바스크립트 런타임란?
런타임이란 프로그래밍 언어가 구동되는 환경을 말한다.
자바스크립트 런타임이란 자바스크립트가 구동되는 환경을 말한다.
이러한 자바스크립트 런타임의 종류로는 웹 브라우저(크롬, 파이어폭스, 익스플로러 등)프로그램과 Node.js 라는 프로그램이 있다.
이러한 프로그램들에서 자바스크립트가 구동되기 때문에 자바스크립트 런타임이라고 한다.
Event loop
개발자가 setTimeout나 XMLHttpRequest, fetch등을 사용하여 비동기 요청을 처리하는 로직을 짰다고 가정해 봅시다. 여기서 자바스크립트를 해석하고 이를 실행할 때 순차적으로 콜스택에 담기게 되고, 실행됩니다. 실행된 함수들 중 setTimeout, XMLHttpRequest 함수 같은 비동기 함수의 경우 브라우저에게 타이머, AJAX 이벤트를 요청한 후 바로 스택에서 제거 된다.
이때, 함수에 포함된 콜백 함수들은 즉시 사라지지 않고 Task Queue라는, 콜백 함수들이 대기하는 큐 형태의 배열에 들어가게 된다. 그리고 이벤트 루프는 호출 스택이 비워질 때마다 큐에서 콜백 함수를 꺼내와서 실행하는 역할을 한다.
Web API
그림의 오른쪽에 있는 Wep API는 JS Engine의 밖에 그려져 있다.
즉, 자바스크립트 엔진이 아니다.
Web API 는 브라우저에서 제공하는 API 로, DOM, Ajax, Timeout 등이 있다.
Call Stack에서 실행된 비동기 함수는 Web API를 호출하고,
Web API는 콜백함수를 Callback Queue에 밀어 넣는다.
콜백함수
콜백 함수는 함수 안에서 어떤 특정한 시점에 호출되는 함수를 말한다. 보통 콜백 함수는 함수의 매개변수로 전달하여 특정 시점에서 콜백 함수를 호출한다. 콜백함수(Callback Function)란 파라미터로 함수를 전달받아, 함수의 내부에서 실행하는 함수이다.
1. 변수나 데이터안에 담길 수 있고
2. 매개변수로 전달 할 수 있고
3. 반환 값으로 사용할 수 있고
4. 실행도중에 생성될 수 있다
일반적인 경우
function plus(a, b) { var sum = a + b; return sum; } function print(result) { console.log(result); } print(plus(1,2));
다음은 plus 함수의 리턴 값을 print 함수로 전달하여 출력하는 예제이다. print함수가 실행되기 위해서는 plus함수의 실행이 전부 완료가 돼야 된다. 즉, print함수는 plus함수의 동작이 모두 끝날 때까지 계속 기다려야 한다
만약 위 코드가 웹사이트의 동작에 해당하는 코드이고 plus함수가 로직이 복잡해서 처리속도가 늦어지게 된다면 실행이 계속 지연되기 때문에 자바스크립트로 이루어진 웹사이트의 모든 동작이 멈춰버리게 된다.
콜백 함수를 사용한 경우
function plus(a, b, callback) { // plus 함수 정의 var sum = a + b; callback(sum); } function print(result) { console.log(result); } plus(1,2, print);
반면에 콜백 함수를 이용하여 동작을 구현하면 처리가 끝날 때까지 기다리는 것이 아니라 처리가 끝나는 시점에서 함수를 호출한다. 즉, 정말로 필요할 때만 함수를 호출해서 효율이 좋고 웹사이트에서도 여러 가지 동작을 동시에 할 수 있다.
이와 같은 방법을 비동기식 처리방법이라고 한다. 현재 웹사이트들은 비동기식 처리방법인 ajax를 활용하여 더 활발하게 동작할 수 있게 되었다. 따라서 콜백 함수는 비동기식 처리방법에 있어서 정말 중요한 개념이라고 할 수 있다.
비동기처리 과정
예시 코드1
예시 코드2
// 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')
결과 출력되는 순서
script start
script end
promise1
promise2
setTimeout
call stack 과 queue 진행되는 순서
→ microtask 와 macrotask 의 queue가 있는데 보통 macrotask queue가 task queue 라고 생각하고 쓴다.
→ micro task: process.nextTick(), Pronuse callback, async function
→ macrotask: setTimeout(), setInterval(), setImmediate()
위의 코드 실행순서
1. console.log(’Start’)
2. setTimeout이 call stack → WEB API 진입후→ macromask queue
3. Promise.resolve() 이 call stack 진입 → then(res⇒ console.log(res) 이 call stack 진입 → res⇒console.log(res) microtask queue 진입
4.console.log(’End!’) call stack 진입→출력
5. microtask queue에 있던 res⇒console.log(res)가 call stack 진입→ 출력
6. macrotask queue 에 있던 setTimeout()이(0초더라도 맨마지막에) call stack에 진입→ console.log출력
결론
macrotask queue에 들어가는 함수들은 그것이 0초라고 하더래도 마지막에 실행한다는걸 알수있음.
왜냐하면 우선순위가 Microtask 가 Macrotask 보다 먼저이기때문이다.
call stack 에 쌓일시 keep없이 바로 처리(출력을 하건 web api, queue로 보내건)
async/await 가 microtask queue로 전달되는 과정
1.console.log(’Before~~’) 부분 call stack 진입후 출력
2. myfunc() 가 call stack 진입→ console.log(’In ~~’) call stack 진입후 출력
3. await one() call stack 진입→ Promise.resolve() call stack 진입→ myFunc() microtask queue 진입
4. console.log(”After~~~”) call stack 진입후 출력
5. myFunc()가 call stack진입후 → console.log(res) call stack 진입후 출력
animation frames
• Animation Frames는 requestAnimationFrame과 같이 브라우저 렌더링과 관련된 task를 넘겨받는 Queue이다.
// 1. 실행
console.log("script start");
// 2. task queue로 전달
setTimeout(function () {
// 10. task 실행
console.log("setTimeout");
}, 0);
//3. microtask queue로 전달
Promise.resolve()
.then(function () {
// 6. microtask 실행
console.log("promise1");
}) // 7. microtask queue로 전달
.then(function () {
// 8. microtask 실행
console.log("promise2");
});
//4. AnimationFrame으로 전달
requestAnimationFrame(function () {
//9. animation frame 실행
console.log("animation");
});
//5. 실행
console.log("script end");
결과 출력순서
script start
script end
promise1
promise2
animation
setTimeout
결론
이벤트루프가 비동기 작업을 처리하는 우선순위는 Microtask Queue→ Animation Frames → Macrotask Queue 순이다.
참고 사이트
'TIL' 카테고리의 다른 글
[TIL] JSX, Udemy 강의 (0) | 2022.03.13 |
---|---|
[TIL] 20220307 프론트엔드/백엔드 (0) | 2022.03.07 |
[TIL] 20220228 검색어를 입력하세요. www (0) | 2022.02.28 |
[TIL] 코딩테스트 (0) | 2022.02.27 |
[TIL] 참조타입!! (0) | 2022.02.26 |