[42장] 비동기 프로그래밍 (1)
자바스크립트 엔진은 싱글 스레드 방식으로 동작한다.즉, 한번에 하나의 task 만을 처리할 수 있다.
싱글 스레드로 동작하는 것은안정성과단순함을 갖는 대신성능적 한계가 있을 수 있다.
이러한 문제를 다루는 내용이 동기 프로그래밍과 비동기 프로그래밍이다.
- 목표
- 동기
- 비동기
📝 동기
실행 컨텍스트 스택에 실행 컨텍스트가 여러개 있으면 FILO(First In Last Out) 구조로 컨텍스트가 실행되고 스택에서 POP 되어 제거 되기 전 까지는 실행 컨텍스트 아래의 다른 실행 컨텍스트는 실행되지 않고 task 가 대기하는 방식이다.
const first = () => {
console.log('First Func...');
}
const second = () => {
console.log('Second Func...');
}
first();
second();
위의 코드의 실행 결과는 당연히 아래의 출력결과가 나올것이다.
'First Func...'
'Second Func...'
그렇다면 아래의 코드는 어떻게 동작할지 생각해보자.
const first = () => {
console.log('First Func...');
}
const second = () => {
console.log('Second Func...');
}
const nested = () => {
nested2();
first();
}
const nested2 = () => {
second();
}
nested();
구조를 먼저 파악해보면 first, second, nested, nested2 함수가 각각 선언되어 있다.
first, second 함수는 단순 출력을 하는 함수이고, nested 는 nested2 가 중첩되어 있는 구조이다.
- 동작 과정
- nested() 함수가 실행되면서 실행 컨텍스트 스택에 nested가 push됨.
- nested() 내부에서 nested2()가 호출되므로, 실행 컨텍스트 스택에 nested2가 push됨.
- nested2() 내부에서 second()가 호출되므로, 실행 컨텍스트 스택에 second가 push됨.
- console.log('Second Func...'); 실행됨.
- second 함수가 실행 컨텍스트 스택에서 pop되어 제거됨.
- nested2 함수가 실행 컨텍스트 스택에서 pop되어 제거됨.
- nested() 내부에서 first()가 호출되므로, 실행 컨텍스트 스택에 first가 push됨.
- console.log('First Func...'); 실행됨.
- first 함수가 실행 컨텍스트 스택에서 pop되어 제거됨.
- nested 함수가 실행 컨텍스트 스택에서 pop되어 제거됨.
- 실행 컨텍스트 스택에는 전역 실행 컨텍스트만 남아 있으며, 함수 실행은 종료됨.
- 출력
'Second Func...'
'First Func...'
위와 같이 복잡하더라도 동기 코드는 함수가 실행된 부분을 잘 추적하면 코드를 이해하는데 어려움이 덜하다.
하지만, 아래의 코드와 같이 시간이 걸리는 함수가 실행되는 경우 전체 코드가 지연되는 단점이 있다.
const first = () => {
console.log('First Func...');
}
const second = () => {
console.log('Second Func...');
}
const heavyFunc = (t) => {
const T = Date.now() + t;
while(Date.now() < T);
first();
}
heavyFunc(5000);
second();
위의 코드는 heavyFunc 에 5000이라는 값이 인자로 전달되면서 Date.now 를 통해 현재 시간 값에 5000 이 더해진 값이 T 가 된다.
여기에서 while 반복문에서 Date.now 가 T 보다 커지면 반복문이 종료되고 first 함수가 실행되어 실행 컨텍스트 스택에서 Pop 되고,
동작이 끝난 heavyFunc 도 실행컨텍스트 스택에서 pop 되어 제거된 후 second 함수가 실행되는 과정을 갖는다.
-출력
'First Func...'
// 5000 time later
'Second Func...'
간단한 동작을 하는 함수의 경우에는 문제가 되지 않지만, 만약 특정 함수가 대용량 데이터를 처리하면서 동작시간이 오래 걸리는 경우
해당 코드 이후에 있는 코드들은 함수가 종료될 때 까지 대기상태에 있다. 그런 점은 Front-End 에서는 로딩시간 증가와 같이 사용자경험을 저해하는 요소로 작용할 수 있다. 때문에 이런 문제를 해결해주는 것이 비동기 방식이다.
- 장단점
장점 | - 코드의 실행이 직관적이다. - 디버깅이 쉽다. - 실행 순서가 보장이 된다. |
단점 | - 시간이 오래 걸리는 작업이 있을 경우 전체 코드가 지연될 수 있다. - UI 가 멈출 수 있다. - 앞선 태스크가 종료될 때 까지 이후의 태스크들이 블로킹된다. |
📝 비동기
현재 실행중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하는 방식이다.
비동기 함수 혹은 이벤트 핸들러는 실행컨텍스트 스택에 push 되고 태스크 스택이라는 임시 보관 영역에 저장된다.
Web API 에서 해당 함수가 특정 시간이 지나거나 응답이 온 경우, 혹은 이벤트 핸들러가 해당 이벤트가 발생한 경우 태스크 큐에 임시 저장되었다가 event loop 에서 실행컨텍스트 스택이 empty 상태임을 감지하면 태스크 큐에서 FIFO(First In First Out) 형태로 임시 저장된 task 가 실행된다.
console.log("Start");
setTimeout(() => console.log("setTimeout"), 0); // 매크로태스크
Promise.resolve().then(() => console.log("Promise then")); // 마이크로태스크
console.log("End");
위의 코드는 어떻게 동작할지 생각해보자.
- 동작 과정
- console.log("Start"); 가 실행 컨텍스트 스택에 push
- "Start" 출력후 실행 컨텍스트 스택 pop
- setTimeout 함수 실행 컨텍스트 스택에 push
- Web API 에 의해 0ms 후 Task Queue 로 임시 저장
- Promise 함수 실행 컨텍스트 스택에 push
- 응답을 받은 후 Micro Task Queue 에 임시 저장
- console.log("End"); 가 실행 컨텍스트 스택에 push
- "End" 출력후 실행 컨텍스트 스택 pop
- 실행 컨텍스트 스택 상태 empty
- event loop 가 실행 컨텍스트 스택의 상태가 empty 인 것을 감지
- Micro Task Queue 에서 Task 확인
- Promise 함수 실행 컨텍스트 스택에 호출 후 pop
- Task Queue 에서 Task 확인
- setTimeout 함수 실행 컨텍스트 스택에 호출 후 pop
- 실행 컨텍스트 스택 상태 empty, 모든 Task Queue 상태 empty
- 종료
- 출력
Start // (1) Call Stack에서 실행
End // (2) Call Stack에서 실행
Promise then // (3) 마이크로태스크 큐 우선 실행
setTimeout // (4) 매크로태스크 큐 실행
동기식과는 반대로 무겁지 않거나 금방 끝나는 task 에 대해서는 비동기로 task 를 처리하는 것은 비효율적이고 불필요한 비동기 사용시 VSC 에서 warning 이 발생한다. 그렇기 때문에 비동기나 동기나 적절하게 사용해야한다.
특히 비동기 방식은 타이머 함수, HTTP 요청, DOM API,파일 읽기, 데이터베이스 접근 같은 task 를 처리할 때 용이하다. 무거운 동작을 비동기로 처리함으로 아직 로딩이 안 된 부분은 로딩 애니메이션을 띄우거나 스켈레톤 UI 를 적용하고 빠른 동작이 가능한 다른 부분을 먼저 로딩시킴으로 보다 사용자로 하여금 사이트 이용 경험을 개선할 수 있다.
- 장단점
장점 | - 시간이 오래 걸리는 작업을 기다리지 않고 다른 작업을 진행할 수 있다. - UI 반응성이 좋아진다. (UX 개선) |
단점 | - 콜백지옥에 빠질 수 있다. - 코드의 흐름이 복잡해질 수 있다. - 예외 처리 및 디버깅이 어려워질 수 있다. |
🔥 배운점
프로젝트를 진행하면서 비동기 async await 을 사용해 서버와 HTTP 로 통신하는 기능을 구현해보았는데 서버와 통신하는 사이 시간지연이 있을 수 있다는 것이 비동기 프로그래밍에 적합한 케이스로 쉽게 이해할 수있었다.
또한 서버가 데이터베이스에서 가져온 값을 클라이언트에 response 로 넘겨주면서 그 값을 처리하는 과정을 비동기로 처리해야할지 동기로 처리해야할지 일부 헷갈렸던 부분이 있었는데 비동기로 처리할지 동기로 처리할지는 데이터의 출처에 따라 구분할 수 있다는 것으로 헷갈렸던 부분을 정리할 수 있었다.
- 참고
https://developer.mozilla.org/ko/docs/Glossary/Asynchronous
비동기(Asynchronous) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN
비동기라는 용어는 둘 이상의 객체 또는 이벤트가 동시에 존재하지 않거나 발생하지 않는 경우(또는 이전 객체 또는 이벤트가 완료될 때까지 기다리지 않고 발생하는 여러 관련 작업)를 말합니
developer.mozilla.org
https://developer.mozilla.org/ko/docs/Glossary/Synchronous
동기(Synchronous) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN
동기적이란 각 참여자가 즉시(또는 가능한 한 즉시) 메시지를 수신(필요시 처리 및 회신)하는 실시간 통신을 의미합니다.
developer.mozilla.org