동기/비동기란?
1) 동기
- 동기란 Request를 보내는 시기와 Response를 받는 시기가 일치.
- 요청을 하게 되면 응답이 올 때까지 프로그램은 정지하고 응답이 오면 다시 진행됨.
const longTimeTask = () => {
// 시간이 오래 걸리는 작업
console.log('시간이 오래 걸리는 작업');
for(let i = 0 ; i < 100000000 ; i++){
console.log(".");
}
};
console.log('시작');
longTimeTask();
console.log('종료');
2) 비동기
- Request와 Response가 동시에 일어나지 않음.
- 비동기로 수행을 할 경우, Request를 보낸 후, Response를 기다리지 않고 다음 작업을 바로 진행.
const longTimeTask = () => {
// 시간이 오래 걸리는 작업
console.log('시간이 오래 걸리는 작업');
for(let i = 0; i < 10000000; i++){
console.log('.');
}
};
console.log('시작');
setTimeout(longTimeTask, 0);
console.log('종료');
*비동기는 종료코드가 먼저 수행된 다음에 시간이 오래 걸리는 작업이 수행됨.
*setTimeout은 대표적인 비동기 코드. 0초 후 실행이더라도 바로 출력되지 않음. (이후 이벤트 루프에 대한 설명 참고)
블로킹/논블로킹
동기 비동기와 블로킹 논블로킹의 의미적 차이.
- 동기와 비동기 : 함수가 바로 return되는지의 여부
- 블로킹과 논블로킹 : 백그라운드 작업 완료 여부
블로킹-동기
- 위 예시에서는 동기 코드는 함수 작업을 기다리기 위해 코드가 멈춤 -> 블로킹
- 그리고 해당 함수의 리턴 값을 (리턴값이 있다고 가정) 기다렸다가 다음 코드를 실행 -> 동기
논블로킹-비동기
- 함수 작업을 넘기고 멈추지 않고 바로 다음 코드를 실행. -> 논블로킹
- 그리고 0초 후에 콜백 함수로 longTime함수를 넣어주었음 -> 비동기
이벤트 루프란?
자바스크립트는 싱글스레드이지만 비동기처리를 이용해 가볍고 효율적이다. 여기서 싱글스레드는 이벤트 루프가 싱글 스레드로 동작하기 때문에 그렇게 불리는 것이다.
단, 실제 구동환경의 경우(브라우저,Node.js등)에서는 주로 여러 개의 스레드를 사용한다.
- ECMAScript 스펙에는 이벤트 루프에 대한 내용이 없다.(es6부터는 조금 달라짐.)
- V8과 같은 JS 엔진은 단일 호출 스택(Call Stack)을 사용하며, 요청이 들어올 때마다 순차적으로 처리.
- 비동기 처리 함수에 대한 내용은 실제로 JS엔진 외부 API에 존재
*Web의 경우 setTimeout, XMLHttpRequest은 Web APIs에 속함
*Node.js의 경우에는 Node.js의 API 호출을 통해 비동기 처리. 이벤트 루프를 통해 스케쥴되고 실행됨.
Javascript의 Run-to-Complete 특징
Run to Complete이란,
- 자바스크립트 함수가 실행되는 방식을 말함.
- 하나의 함수가 실행되면, 이 함수의 실행이 끝날 때까지는 다른 어떤 작업도 중간에 끼어들지 못한다는 의미.
- 즉 현재의 스택에 쌓여있는 모든 함수들이 실행을 마치고 스택에서 제거되기 전까지는 다른 어떤 함수도 실행 될 수 없음.
const longTimeTask = () => {
// 시간이 오래 걸리는 작업
console.log('시간이 오래 걸리는 작업');
for(let i = 0; i < 10000000; i++){
console.log('.');
}
};
const asyncTask = () => {
//비동기 실행 함수.
console.log("asyncTask");
}
console.log('시작');
setTimeout(asyncTask,0);
longTimeTask();
console.log('종료');
//0초 후에 바로 실행되는 것이 아닌,
//call stack에 쌓인 longTimeTask의 모든 stack이 비워진 후,
//asyncTask가 실행된다.
*setTimeout은 실제 설정된 timeout보다 늦게 실행됨.
태스크 큐와 이벤트 루프.
setTimeout에서 실행되는 함수는 어디서 대기하고 있다가 누구를 통해 실행될까?
- 태스크 큐는 콜백함수들이 대기하고 있는 큐(FIFO)형태의 배열
- 이벤트 루프는 CallStack이 비워질 때마다 테스크 큐에서 콜백 함수를 꺼내와서 실행하는 역할.
- setTimeout에서 정한 시간이 지나면 그 함수를 바로 실행한다기 보다는 테스크 큐에 추가한다.
- 이벤트 루프는 현재 실행중인 테스크가 종료되면 테스크큐에 대기중인 첫 번째 태스크를 실행한다.
즉 모든 비동기 API들은 작업이 완료되면 콜백 함수를 태스크 큐에 추가.
이벤트 루프는 ‘현재 실행중인 태스크가 없을 때’ (주로 호출 스택이 비워졌을 때) 태스크 큐의 첫 번째 태스크를 꺼내와 실행.
[참고 http://www.2ality.com/2014/09/es6-promises-foundations.html]
setTimeout(fn,0) 을 사용하는 이유
프론트엔드의 JS 코드를 보다보면 setTimeout(fn,0)과 같은 코드를 종종 목격하게 된다.
이벤트 루프에 대한 이해가 없이 이 코드를 보게되면, 별로 의미가 없는 코드 처럼 느껴진다. 하지만 실제 코드는 그냥 fn을 실행되는 것과 다른 프로세스를 따르게 됨.
- setTimeout함수는 콜백 함수를 바로 실행하지 않고 호출 스택이 아닌 테스크 큐에 추가
setTimeout(function() {
console.log('A');
}, 0);
console.log('B');
//B A 출력
- FE에서는 렌더링 엔진과 관련해서 요긴하게 쓰임.
- 렌더링 엔진은 JS엔진과 함께 단일 태스크 큐를 통해서 관리됨.
$('.btn').click(function() {
renderWait();
mainFunc();
hideWait();
showResult();
});
- 일반적으로 renderWait를 통해서 대기화면을 뿌린후, mainFunc가 실행될 것이라고 예상하겠지만, 실제로는 renderWait부터 showResult까지 실행된 후, 렌더링 task가 실행되게 된다.
- renderWait를 통해서는 렌더링요청을 task큐에 추가시키게 될 테지만, 이를 위해서는 현재 실행중인 Call stack이 모두 비워져야함.
$('.btn').click(function() {
renderWait();
setTimeout(function() {
mainFunc();
hideWait();
showResult();
}, 0);
});
- 따라서 위와 같이 renderWait이후 코드를 다른 태스크로 나누어 주면, 예상한 순서대로 실행이 될 수 있다.
renderWait -> 렌더링 task 실행 -> setTimeout내 함수 task 실행 - 참고로 setTimeout에서 0이라는 숫자는 실제 ‘즉시’를 의미하지 않음. 부라우저 내부적으로 타이머 최소 tick을 정하여 관리하는데 이 최소단위만큼 지난 후, task 큐에 추가된다고 보면됨.
(크롬 브라우저는 최소단위 tick이 4ms 즉, setTimeout(fn,0)은 setTimeout(fn,4)라고 보면됨)
Promise와 이벤트 루프
예시를 통해 먼저 Promise의 행동방식을 파악해보자.
setTimeout(function() { // (A)
console.log('A');
}, 0);
Promise.resolve().then(function() { // (B)
console.log('B');
}).then(function() { // (C)
console.log('C');
});
- 해당 코드는 B->C->A 순서로 실행.
- Promise는Micro Task를 사용하게 되기 때문.
Micro task란?
- 일반 태스크보다 더 높은 우선순위의 task.
- setTimeout함수의 콜백 A는 일반 task 큐에 추가됨.
- Promise의 then() 메서드 콜백 B와 C는 일반 task가 아닌 마이크로 task큐에 추가된다.
- 이벤트루프는 micro task 큐가 먼저 비었는지 확인 후, 일반 task 큐를 보게 됨.
- 따라서 BCA순서로 출력.
- HTML 스펙에서 perform a microtask checkpoint 항복에 명시
[참고]
'Language > Javascript' 카테고리의 다른 글
[Javascript] Promise의 기본 (1) | 2022.10.04 |
---|---|
[Javascript] ES6 Array helper (0) | 2021.11.19 |
[Javascript] Scope와 변수 선언 (var, let, const) 키워드 차이점 (0) | 2021.11.12 |
[Javascript] Hoisting이란? (0) | 2021.11.11 |
[Javascript] ES6 문법 정리 (0) | 2021.11.10 |