앞선 글에서 내가 생각하기에 자바스크립트에서 개념적으로 가장 중요한 2 파트가 있는거 같다고 언급한 적이 있었다.
그중 두번째가 지금 알아볼 파트다
저번 중요 파트에서 개인적으로 자바스크립트의 "영리함", "치밀함"을 느꼈다면,
이번 파트에서는 자바스크립트의 "친화력"을 느꼈다
무슨 말일까?
이 글에서 알아볼 것
- 동기(sync) & 비동기(async) 코드
- 콜백 함수
- 자바스크립트 엔진과 브라우저의 의사소통 (이벤트 루프)
- async / await 키워드
이번 글 또한 앞선 글들과 같이 목차를 나눠놨지만,
개념들이 서로 엉켜있어 목차의 의미가 앞선 글들보단 희미할것이다.
정리 또한 목차를 따르겠지만, 밑에서 살펴볼 개념이 앞선 개념을 살펴볼때 나올 수도 있다
1. 동기(sync) & 비동기(async) 코드
동기, 비동기 코드가 뭔지 알아보기 전에 알아야할 사전지식이 존재한다.
"자바스크립트는 싱글 쓰레드다"
다른 언어를 공부한 경험이 있다면, 싱글 쓰레드와 멀티 쓰레드의 작업 방식 차이가 개발 환경에 큰 영향을 미친다는 것을 알고있을 것이다.
하지만 이 용어를 처음 접한다면, 싱글 쓰레드가 뭔데 강조하지? 생각이 들것이다.
그러면 차례차례 싱글 쓰레드가 뭔지, 동기 & 비동기 코드가 뭔지 알아보겠다.
- 싱글 쓰레드
- 한번에 하나의 작업만 할 수 있다.
- 따라서, 작업 하나가 끝나야 다음 작업을 수행한다
- 동기(Sync) 코드
- 이렇게 작업을 동시에 진행하지 않고, 하나씩 처리하는 것을 sync code라 한다
이렇게만 보면, 싱글 쓰레드라는 것은 치명적인 단점이 아닐까 생각 할 수도 있다.
왜냐하면, 우리가 보통 웹 또는 앱에서 하는 작업들은 동시에 일어나야 하는 경우가 많다
(만약, 무언가를 다운받을 동안 다른 작업을 아무것도 못한다면 상당히 답답할 것이다)
물론, 싱글 쓰레드를 활용하면서 발생하는 장점도 존재하고, 꼭 싱글 쓰레드로 작업해야하는 경우도 존재할것이다.
하지만, 위에서 언급한 단점은 현대에 와서는 반드시 해결하고 가야하는 문제점이다.
그렇다면 자바스크립트는 이 문제점을 어떻게 해결하였을까?
- 비동기(Async) 코드
- 마치 멀티 쓰레드처럼 작업을 하는 코드다.
- 이러한 비동기 코드가 자바스크립트에 존재하고, 활용할 수 있다.
이상하다,
분명 자바스크립트는 싱글 쓰레드라고 했는데, 어떻게 이런게 가능한지 의문이 생긴다
비동기 코드가 기존 자바스크립트와는 다른 매우매우 특별한 코드라 가능한걸까?
사실은 멀티 쓰레드인데 세상 사람들 전체가 다 알고있는 사실을 나만 잘못알고있는것일까?
둘다 아니다.
내가 이 글 서론에서 이 파트를 공부하면서 자바스크립트의 "친화력"을 느꼈다고 하였는데,
이제부터 비동기 코드를 원리를 살펴보면서 이 느낌을 느껴보자
- 결론부터 말하자면, 브라우저를 이용해서 가능한 것이다.
- 브라우저를 이용한다는게 무슨 뜻일까?
- async code를 사용하면 브라우저에서 오프로드(Off-load) 시킬 수 있다.
- 즉, async code 로 처리되어있는 작업들은 브라우저가 담당하게되는 것이다.
- 자바스크립트 엔진에서는 sync 코드들을 차례차례 처리하고 있을 때, async 코드들의 처리는 브라우저가 하는 것이다...
- 여기까지 보면, 자세한 동작 방식은 아직 모르겠지만, 브라우저를 잘 활용해서 마치 멀티쓰레드 처럼 행동할 수 있다는 것을 이해할 수 있다.
- 브라우저를 이용한다는게 무슨 뜻일까?
- 우리는 여태까지의 경험을 통해 위의 설명에서 중요한 뭔가가 빠져있다는 것을 느낄 수 있다.
- 지금까지 거의 대부분 무엇이 다른 무엇과 소통하려면 그 사이에 연결 다리 역할이 필요했다는 점이다.
- 이러한 의사소통 연결다리가 "콜백 함수'다.
< 2, 3번 글을 확인하고 아래 내용을 확인하자>
우리는 이렇게,
자바스크립트가 싱글 쓰레드라는 한계를 어떻게 해결했는지 차례차례 살펴봤다.
그러면 다시 1번 주제로 돌아와 Sync 코드와 Async 코드는 어떻게 실행되는지, 언제 사용되는지 알아보자
- 예시를 통해 Sync Code & Async Code 실행방식 이해하기
const dummyFn = () => {
console.log("클릭!");
};
button.addEventListener("click", dummyFn);
let result = 0;
for (let i = 0; i < 100000000; i++) {
result += i;
}
console.log(result);
- 위의 코드처럼 반복문을 통해 엄청 큰 연산을 수행하고, 브라우저가 로드되자마자 버튼을 클릭하면 어떻게될까?
- 사진처럼 아무리 버튼을 클릭해도 연산이 마무리될때까지는 이벤트가 실행되지 않는다.. 왜그럴까?
- 이벤트 리스너는 보통 브라우저로 전달되어, 브라우저에서 작업 담당한다.
- 이벤트 리스너도 Async 코드다 (https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing 참고)
- 이때, 자바스크립트는 for 연산을 처리하기 시작할꺼고, 이 작업은 상당한 시간이 필요하다.
- 따라서, 브라우저에서 이벤트 리스너에 대한 작업을 완료하고 자바스크립트로 보내도, 자바스크립트는 아직 for 연산을 처리하고 있기 때문에 이벤트 작업을 수행할 수 없다. 왜???
- 자바스크립트는 기본적으로 싱글 쓰레드니까..브라우저의 도움을 받아 멀티 쓰레드처럼 행동하지만, 근본적으로 싱글 쓰레드라는 것을 기억해야한다.
- 이벤트 리스너는 보통 브라우저로 전달되어, 브라우저에서 작업 담당한다.
- 사진처럼 아무리 버튼을 클릭해도 연산이 마무리될때까지는 이벤트가 실행되지 않는다.. 왜그럴까?
const sucLocationCB = (curData) => {
console.log(curData);
};
const errLocationCB = (errorData) => {
console.log(errorData);
};
const dummyFn = () => {
navigator.geolocation.getCurrentPosition(sucLocationCB, errLocationCB);
console.log("기다리는중......");
};
button.addEventListener("click", dummyFn);
- 위치를 얻는 코드(Async 코드)를 이벤트 헨들러 함수에 정의하고, 밑에 더미 로그를 작성하였다…결과를 살펴보자
- 결과 이미지에서 볼 수 있듯이 더미로그가 먼저 출력되고, 위치가 출력되었다 (error가 발생한건, 내가 위치 권한을 거부해서…)
- 위치를 얻는 코드는 Async 코드다. 그렇다면, 해당 코드는 브라우저에서 오프로드하여 브라우저에서 작업을 수행한다.
- 브라우저에서 해당 작업을 아무리 빨리 처리한다하여도, 자바스크립트는 이미 아래 코드로 이동하여 console.log("기다리는중......"); 코드를 call stack에 푸쉬하여 수행한다.
- 그렇다면, 브라우저에서 해당 작업을 완료했어도 메시지 대기열에서 더미로그 작업이 끝날때까지 실행대기 상태로 기다려야한다.
- 결과 이미지에서 볼 수 있듯이 더미로그가 먼저 출력되고, 위치가 출력되었다 (error가 발생한건, 내가 위치 권한을 거부해서…)
결론은,
브라우저를 통해 멀티 쓰레드같은 행동을 하지만,
근본적으로 자바스크립트는 싱글 쓰레드임을 기억하고, Sync 코드와 Async 코드의 실행 방식을 잘 숙지하자
2. 콜백 함수
- 앞에서 말했듯이, async code를 작성할때 필요한 코드로, 자바스크립트와 브라우저의 의사소통 수단으로 사용된다
- 의사소통의 방법은, 브라우저에서 담당한 코드에 기본적으로 콜백함수가 포함되어 있고, 작업이 끝나면 스크립트에 호출하는 방식으로 의사소통하는 것이다.
<아래 정리를 살펴보기 전에 3번을 먼저 살펴보고 오자>
const errLocationCB = (errorData) => {
setTimeout(() => {
console.log(errorData);
}, 2000);
};
const dummyFn = () => {
navigator.geolocation.getCurrentPosition(sucLocationCB, errLocationCB);
setTimeout(() => {
console.log("이건 언제 실행될까...?");
}, 0);
console.log("기다리는중......");
let result = 0;
for (let i = 0; i < 100000000; i++) {
result += i;
}
console.log(result);
};
button.addEventListener("click", dummyFn);
위의 코드를 살펴보면서 콜백함수의 특징 두가지를 알 수 있다.
- 콜백함수 안에 콜백함수를 정의할수있다. 즉, 중첩 콜백함수가 있다.
- getCurrentPosition(콜백함수) → errLocationCB(콜백함수) → 타이머함수(콜백함수)
- async 코드는 아무리 빨리 실행된다하더라도, 결국 브라우저로 넘어갔다 오는 걸 기억하자
- setTimeout()에 최소시간인 0을 설정하고, 더미로그에는 비교적 시간이 걸리는 for문을 작성하여도 for문이 먼저 출력된다
- 다시한번 강조하지만, 자바스크립트는 싱글 쓰레드다…async 코드(0초로 설정된 setTimeout())는 뭐가되었던, 일단 브라우저로 넘어간다…그 순간 자바스크립트는 바로 아래줄로 이동하기때문에 async가 아무리 빨리 끝나도 이미 다른 코드가 실행중이다…
*참고
- setTimeout()에서 0초로 설정한다고, 무조건 0초가 걸리는 것이 아니다. 이는 수행시간의 최소시간을 설정한것일뿐이다.
3. 자바스크립트 엔진과 브라우저의 의사소통 (이벤트 루프)
앞의 이야기들을 정리해보자면,
자바스크립트는 명백한 싱글 쓰레드다.
하지만, 멀티 쓰레드처럼 활용할 수 있는데, 그것이 Async 코드를 활용하는 것이다.
이 Async 코드는 자바스크립트 엔진에서 처리하는 것이 아닌, 브라우저에서 처리한다.
이러한 자바스크립트 엔진 - 브라우저의 의사소통에는 콜백함수가 사용된다.
이제 사전지식을 하나가 살펴보고,
자바스크립트 엔진 - 브라우저의 의사소통에 대해서 자세히 살펴보자
- 이벤트 루프
- 이벤트 루프란 async code 처리를 돕는다
- 정확히 말하면, async code를 사용하는 콜백함수의 처리를 돕는다
- 이벤트 루프는 자바스크립트 엔진 환경의 일부가 아니라, 자바스크립트의 호스트 환경 중 일부이다
- 즉, 자바스크립트 엔진을 사용하는 항목 중 하나
- 여기선 브라우저를 뜻한다
- 항상 실행중 상태로, 이벤트 루프가 수행되어야할때를 확인한다
- 이벤트 루프란 async code 처리를 돕는다
이 이벤트 루프를 잘 기억하면서 아래 내용을 살펴보자
- 자바스크립와 브라우저가 코드를 처리하는 자세한 방식
- 자바스크립트는 기본적으로 위에서 아래로 코드를 읽어간다.
- 이떄, Async 코드를 발견하면 브라우저에서 오프로드하여 브라우저 담당으로 가져간다
- 그럼 자바스크립트는 그 Async 코드 다음 코드를 읽고, 실행한다 (A 함수라가정)
- A함수의 작업이 수행되는 중간에 브라우저에서 Async 코드의 작업이 완료되면 어떻게될까? 바로 자바스크립트에게 보낼까? 아니다…
- 메시지 대기열(Message Queue)에 브라우저에서 처리 완료된 코드를 저장한다.
- 메시지 대기열(Message Queue란?
- 브라우저가 자바스크렙에 시간이 생길때에 실행해야할 모든 코드를 저장해놓는다
- 즉, 실행대기 상태가 되는 것이다. (예전 글 중 하나인 stack & heep을 확인하자 https://joseph0926.tistory.com/9 )
- call stack에서 앞선 작업들이 끝나 비워지면, 대기중인 코드가 실행되어야하는데. 이떼 사용되는 것이 이벤트 루프다
- 브라우저가 자바스크렙에 시간이 생길때에 실행해야할 모든 코드를 저장해놓는다
- 메시지 대기열(Message Queue란?
- 이벤트 루프가 수행된다
- 이벤트 루프가 수행된다는 것은(역할은)?
- 자바스크립트 엔진의 call stack을 대기중인 메시지(메시지 대기열에 저장되어 실행 대기중인 코드)와 동기화하는 역할을 수행한다
- call stack이 비어있는지 확인
- 비어있다면, 대기 중인 작업이 있는지 확인
- 대기 중인 코드가 존재한다면, 해당 코드를 call stack으로 푸쉬한다
이렇게 자바스크립트 엔진과 브라우저의 의사소통에 대해서 자세히 알아보았다.
다시 1번으로 돌아가자
4. async / await 키워드
- 프로미스를 활용하면 async 코드를 콜백중첩 없이 깔끔하게 작성할 수 있고, then(), catch()를 이용하여 로직의 흐름도 쉽게 파악할 수 있다.
- (프로미스에 대한 것은 따로 글을 작성할 예정이다)
- 이 프로미스에 then, catch를 사용하여 코드를 짜는 방법도 이미 훌룡하지만, 최신 자바스크립트에는 더욱 발전시킨 방법이 존재한다...바로 async / await 키워드를 활용하는 구문이다.
- async / await 키워드란?
- 오직 함수에서만 사용 가능하다
- 여전히 프로미스를 활용하지만, then과 catch를 생략할 수 있어, 좀더 sync 코드처럼 보이게 해준다
- async / await 사용법
- 함수 앞에 async 키워드를 붙인다
- async function dummyFn () {} or const dummyFn = async () ⇒ {}
- 이 키워드가 붙은 함수는 자동으로 프로미스를 반환한다
- 프로미스 앞에 await 키워드를 붙인다
- await는 프로미스가 성공 or 실패하면 자동으로 다음 코드를 실행시켜줌
- 그러면 드는 의문이,, 스크립트 실행을 블로킹하는것 아닌가? 싶겠지만, 결과적으로 아니다. 아래 실행방식을 확인하자
- await는 프로미스가 성공 or 실패하면 자동으로 다음 코드를 실행시켜줌
- 함수 앞에 async 키워드를 붙인다
- async / await 실행방식
- async가 해당 키워드가 붙은 큰 프로미스의 모든 프로미스를 감싸고, await가 붙은 프로미스가 해결될때까지 기다렸다가 해결되면 이것을 큰 프로미스로 반환한 후 저장된다(보이지 않는 then 블럭을 추가되고. 그 then 블럭은 프로미스의 결과를 반환 받아 큰 프로미스에 저장되는것)
- 즉, async나 await는 자바스크립트 기존의 실행 방식을 바꾸는 것이 아닌, 코드를 내부적으로 바꿔주는 것이다.
- async가 해당 키워드가 붙은 큰 프로미스의 모든 프로미스를 감싸고, await가 붙은 프로미스가 해결될때까지 기다렸다가 해결되면 이것을 큰 프로미스로 반환한 후 저장된다(보이지 않는 then 블럭을 추가되고. 그 then 블럭은 프로미스의 결과를 반환 받아 큰 프로미스에 저장되는것)
- async / awiat 오류 처리
- await는 프로미스의 결과를 기다리고, 결과가 나오면 바로 다음줄로 넘어가는데, 그렇다면 오류는 어떻게 처리할까?
- async / await 키워드란?
const dummyFn2 = async () => {
try {
const timeData = await setTimer(2000);
} catch (error) {
console.log(erro);
}
console.log(timeData);
}
-> try - catch 로 감싸준다
- async / await는 정말로 훌룡한 기능이다.. 하지만 여기에도 단점이 존재한다
- 동일한 함수 내에서 동시에 작업을 실행할 수 없음
- 오직 함수에만 사용가능
이번 글에서 알아본 내용은 앞으로 자바스크립트를 이해하고, 활용하는데 매우매우 중요한 역할을 할 것이다.
따라서, 이에 대한 이해가 필요하고, 공부가 필요하다.
다음 글에서는,
모든 웹 프로그래밍 기술에서 소통을 위해 사용되는 내용을 알아볼것이다.
'OLD > Javascript' 카테고리의 다른 글
[Javascript] 자바스크립트 모듈 (0) | 2023.02.26 |
---|---|
[Javascript] 자바스크립트 HTTP 통신 (XMLHttpRequest vs fetch() vs axios ...) (0) | 2023.02.26 |
[Javascript] 자바스크립트 Number & String (부동 소수점, BigInt, 태그된 템플릿) (0) | 2023.02.24 |
[Javascript] 자바스크립트 고급 함수 (순수 함수 & 부수 효과, 팩토리 함수, 클로저) (0) | 2023.02.23 |
[Javascript] 자바스크립트 이벤트(preventDefault(), 버블링(Bubbling) & 캡쳐링(Capturing), 드래그 & 드롭) (2) | 2023.02.22 |