"자바스크립트는 멀티스레드가 아닌 싱글스레드를 사용하는 언어이다. 하지만 하나의 스레드에서 비동기적 실행을 통해 멀티스레드처럼 작동하는 것이다.”
비동기적 실행이 없는 자바스크립트는 팥 없는 붕어빵이며 탄산 없는 콜라와 같을 것이다. 자바스크립트를 보다 잘 이해하기 위해 비동기/동기가 무엇인지, 자바스크립트에서는 비동기를 구현하기 위해 어떤 기능들을 가지고 있는지 등에 대해 알아보려한다.
동기적(Synchronous) - ex) 영화매표소
현재 실행중인 작업이 종료될 때까지 다음에 실행될 태스크가 대기하는 방식
- 특징
- 코드가 처음부터 끝까지 순차적으로 실행
- 속도가 느리다
- 실행순서 보장
- 진행방향이 일방향이기에 코드에서 에러가 발생하면 어디인지 파악이 쉽다
비동기적(Asynchronous) - ex) 식당
현재 실행중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하는 방식
- 특징
- 속도가 빠르다
- 어느것이 먼저 끝나고 진행되는지 모르기에 진행방향을 예상하기 힘들다(에러파악 힘듦)
점점 더 진화하고 다양해지는 웹환경에서 모든 것을 동기적으로 실행하게 된다면 하나의 페이지를 보기 위해서도 오랜 시간 기다려야 하는 불편함이 발생할 것이기에 특히 싱글스레드로 작동하는 자바스크립트의 특성 때문에 Non-blocking을 위한 비동기처리는 더더욱 필수적이고 그 중요도 또한 대단하다!
보다 쉬운 이해를 돕기 위해 "집청소"를 한번 생각해 보자 🧹
오늘은 신나는 토요일, 밀린 집안일을 하려 하는데 크게 “화장실청소, 설거지, 세탁기 돌리기, 건조기 돌리기” 정도가 있다. 혼자 자취를 하기 때문에 혼자서 모든 일을 감당해야 한다(싱글스레드의 방식). 다른 사람이 있었다면 동시에 여러 가지를 했을 테지만..?
동기적으로 청소를 진행했다면 최악의 상황으로, 먼저 빨래를 돌리고 한 시간 정도를 기다린 뒤 빨래를 꺼내 건조기에 넣고, 또 1시간 뒤 건조까지 마무리가 되면 화장실청소를 하고 청소가 끝나면 설거지… 하지만 아무도 이런 식으로 청소를 하진 않을 것이다. 보통은 제일 처음 빨래를 돌리고 그 시간 동안에 화장실청소와 설거지를 할 것이고 그 시간동안에 화장실청소와 설겆이를 마무리하고 빨래가 끝나면 건조기를 돌릴 것이다.(비동기적 청소) 이처럼 세탁기를 (백그라운드에) 돌려놓고 그 시간에 나머지 청소를 함으로써 동기식보다 좀 더 빠르게! 효율적으로! 청소를 할 수 있는 것이다.
🔥BUT🔥️
위에서 말한 비동기적 청소를 사람이 아닌 로봇이 한다고 생각해 보자.
“건조기 돌리기”는 비동기적으로 돌려놓은 세탁기가 끝나야지만 실행되어야 하는 단계이다. 그렇지 않고 단순히 순서대로 화장실청소, 설거지가 끝나고 건조기를 돌려버리면 건조기가 고장 나게 되면 에러가 발생할 것이다. 이를 위해 자바스크립트는 몇 가지 방식을 정해놓았다.
자바스크립트의 비동기 구현방식
1. 콜백함수(callback)
✏️ 콜백함수란?
- 다른 함수의 인자로써 이용되는 함수
- 어떤 이벤트에 의해 호출되는 함수
- called at the back..?
쉽게 말하면 특정한 함수가 끝난 뒤 다음 작업을 진행하고 싶을 때 사용되는 함수이다. 세탁기 돌리기와 같은 비동기 작업이 끝난 뒤 건조기 돌리기를 실행해야 하기에 콜백함수를 사용함으로 구현할 수 있다.
function runWashingMachine(callback) {
console.log("세탁기를 돌리는 중...");
setTimeout(function () {
console.log("세탁기 작업 완료.");
callback();
}, 3000);
}
function runDryer() {
console.log("건조기를 돌리는 중...");
setTimeout(function () {
console.log("건조기 작업 완료.");
}, 2000);
}
runWashingMachine(runDryer);
하지만 비동기적 구현이 늘어나면 그만큼 콜백의 깊이(?)는 깊어지게 되고 콜백이 점점 중첩되어 콜백 지옥(callback hell)에 빠지는 문제가 발생하였다. 또한 실행 순서가 어떻게 진행되는지 일일이 따라가면서 작성하고 해석하는 매우 비효율적인 일이 발생한다. 코드 자체도 복잡해져 가독성이 떨어지기에 관리하기에도 매우 어렵고 특히 에러처리, 예외처리에 있어 힘들다는 문제들이 발생했다.
이런 문제들로 인해 ES6에 Promise(프로미스) 객체가 등장했다.
2. Promise(프로미스)
프로미스는 콜백 패턴이 가진 단점들을 보완하며 비동기 처리 시점을 명확하게 표현할 수 있다는 장점을 가지고 탄생하였다.(콜백을 예측가능한 패턴으로 사용할 수 있게 한다)
promise에 대한 자세한 내용들은 이곳에서 확인할 수 있다.
3. async / await
비동기 처리를 위해 ES7에 등장한 async / await은 콜백과 프로미스의 크고 작은 문제들을 보완하며 특히 프로미스를 훨씬 편! 하! 게! 사용할 수 있게 되었다.
function 앞에 async를 붙이면 해당 함수는 항상 프라미스를 반환하게 된다. 프라미스가 아닌 값을 반환하더라도 이행 상태의 프라미스(resolved promise)로 값을 감싸 이행된 프라미스가 반환되도록 한다.
그리고 await 키워드를 만나면 자바스크립트는 프로미스가 처리될 때까지 기다린다. 즉, Promise의. then()을 세련되고 쉽게 result 값을 얻도록 해준다.
자세한 내용들은 이곳에서 확인할 수 있다.
그럼 언제 비동기적 처리가 필요할까?
- 사용자 이벤트 처리
브라우저 화면에서 사용자가 무엇을 할지는 예측이 불가능하다. 따라서 이런 화면 이벤트를 관리담당하는 녀석에게 우리는 특정이벤트가 발생할 때!!! 호출(call)을 원하는 내용을 (back 하는) callback 함수에 전달하게 된다.
- 네트워크 응답 처리
화면에서 서버에게 요청을 보냈을 때, 그 응답이 언제 올지 알 수 없다. 따라서 이런 서버에 대한 응답처리 등도 비동기적으로 처리해야 한다.
ex) Ajax, Request, Fetch, Axios
- 의도적으로 시간 지연을 사용하는 기능
ex) setTimeout, setInterval, 알람, 구글 캘린더 등등
마치며
앞서 이벤트 루프에 대해 공부하며 더 나은 이해를 위해서는 동기/비동기에 대한 이해가 필수적이라는 것을 깨달았고 그 이유로 이번에는 동기 / 비동기에 대해 정리해 보았다. 내가 사용했던 async / await가 어떻게 작동하고 왜 사용하였는지에 대해 이해할 수 있었다. 그전에는 async를 쓰면 그냥 await를 써야 하는구나, 서버에 뭔가 요청하고 받을 때 사용해야 하는 거구나 등 얕은 이해를 가지고 사용했던 날들이 생각나기도 한다. 이렇게 나의 부족함을 매번 깨닫게 되지만 부족한 점들을 깨닫고 하나하나 그 부분을 채워나가며 얻는 즐거움이 크기에 감사하다.
참조
https://ko.javascript.info/async-await
https://poiemaweb.com/es6-promise
'TIL' 카테고리의 다른 글
async / await (0) | 2023.03.29 |
---|---|
Promise(프로미스) (0) | 2023.03.28 |
Libuv 라이브러리(feat. 이벤트 루프) - 3 (0) | 2023.03.24 |
V8엔진 구조 및 작동 방법 - 2 (0) | 2023.03.23 |
나는 왜 Node.js를 사용했을까? - 1 (0) | 2023.03.15 |