-
비동기 자바스크립트Web/자바스크립트 2021. 2. 20. 19:29반응형
■ 비동기 코드는 동기 코드와 비교해서 설명하면 간단하게 이해할 수 있다.
먼저, 동기(synchronous) 코드는 말 그대로 '동기화'를 생각하면 된다.
코드가 작성된 순서와 코드가 실행 완료되는 순서가 동일하다, 혹은 동기화되어있다.
반면, 비동기(asynchronous) 코드는 코드의 작성 순서와 코드의 실행 완료 순서가 동기화되어있지 않다.
그래서 상단에 작성된 코드가 하단에 작성된 코드보다 나중에 실행이 완료될 수도 있다.
다음과 같은 경우를 생각해보자.
const image = document.getElementById('img').src; processLargeImage(image, () => { console.log('Image Processed!'); });
■ processLargeImage, 즉 커다란 이미지를 처리하는 함수가
이미지를 처리하기까지 일정한 시간이 걸릴 것이라는 것을 대충 예상할 수 있을 것이다.
하지만 우리는 뒤에 있는 나머지 코드들이 저 처리가 끝날 때까지 기다리기를 원하지 않는다.
코드가 중단된다는 것은, 때때로 치명적인 상황으로 흘러갈 수 있기 때문.
■ 근데 만약 저 이미지 처리가 끝나고, 그 처리된 이미지를 활용해서 뭔가 할 일이 있다면?
우리는 전체 코드들이 이미지 처리가 끝날 때까지 기다리기를 원하지 않지만
특정 코드들은 이미지 처리가 끝난 후에 실행되기를 원한다.
■ 이런 경우에 우리가 사용할 수 있는 방법은, 저 함수에 '이미지가 처리된 후에 실행될 콜백 함수'를 넘겨주는 것이다.
(콜백 함수는 '특정 로직이 끝나고 나면 실행되는 함수' 정도로 생각하면 편하다.
특정 메소드에 콜백 함수가 붙여지면, 그 메소드의 모든 동작이 끝나고 나서야 콜백 함수가 실행되는 것)
이렇게 하면, 우리는 함수가 끝날 때까지 기다리지 않으면서도, 그 함수의 결과로 무언가 일을 할 수 있다.
우리는 그 함수가 background 에서 자신의 일을 하도록 내버려 두는 것이다.
그러면 해당 함수가 background 에서 일을 하는 동안, 나머지 코드들은 계속해서 실행된다.
그리고 processLargeImage가 끝나고 나면 'Image Processed!' 라는 문자열이 출력되도록
해당 콜백함수를 인자로 넘겨줬기 때문에, 그 과정(이미지가 처리된 후 'Image Processed!'를 출력하는 과정)또한
나머지 코드들이 실행되는 동안 순차적으로 알아서 잘 실행될 것이다.
이렇게 하면 코드가 block될 일은 없게 된다.
■ 정리하자면, 우리는 전체 코드가 중간에 block되지 않기를 바라지만
코드가 block되고, 해당 업무가 끝날 때까지 기다렸다가 실행되어야만 하는 행동들이 있다.
이 때, 코드의 block이 필요한 행동들을 미래로 미루기 위해서 콜백 함수를 이용할 수 있다는 것.
즉, 특정 로직이 온전히 끝나고나서 실행되기를 원하는 동작들을 '콜백 함수화' 시키면 된다.
■ 여기서, 비동기 코드가 background 에서 어떻게 실행되는가를 설명하기 위해서는
'Event Loop(이벤트 루프)'라는 개념에 관한 이해가 필요하다.
■ Event Loop(이벤트 루프)는 우리가 함수를 호출하거나 DOM Event같은 event들을 다룰 때
자바스크립트의 뒷편에서 벌어지는 커다란 context(문맥, 과정)의 일부이다.
참고로, 여기서부터의 내용은 아래의 코드가 벌어지는 과정을 최대애애한 상세하게 천처어언히 묘사할 것이므로
아래의 코드가 벌어지는 내용이 눈에 선하게 그려진다 하는 분들은 안 보셔도 됩니당.
const second = () => { setTimeout(() => { console.log(`Async Hey There!`); }, 2000); }; const first = () => { console.log(`Hey There!`); second(); console.log(`The end`); }; first();
■ 우선, first 함수를 실행하게되면, first 함수의 execution context(함수 호출 시, 해당 함수의 정보를 담은 문맥)가
execution stack(= call stack, 말 그대로 함수의 execution context를 쌓아놓는 스택 자료구조)에 쌓이게 된다.
그 다음, first 함수 안에서 console.log(`Hey, there!`)가 실행되면
log 함수의 execution context가 또 call stack의 맨 위에 쌓이게 된다.
그 다음에 log 함수가 return을 하게 되고, log 함수의 execution context는 call stack에서 사라진다.
second 함수로 진행하면서, 새로운(second 함수의) execution context가 생성되고 쌓이고,
그 다음에 second 함수 내부에서 setTimeout이 호출되면서 setTimeout의 execution context가 그 위에 쌓인다.
여기까지 보면, global execution context(전역 context) 위에 first 함수, 그 위에 second 함수,
맨 위에는 setTimeout 함수의 execution context가 차례대로 쌓여있는 상황이다.
■ 참고로, setTimeout함수는 자바스크립트 엔진 외부에 존재하는 'Web APIs'의 일부이다.
setTimeout에 의해서 생겨난 타이머는 2초동안(인자로 2000ms를 넣어줬으니깐) 이 Web APIs에서 동작하게 되고,
(DOM 조작 함수, setTimeout, AJAX를 위한 HTTP 요청, geolocation, localStorage 등이 동작하는
자바스크립트 엔진의 백스테이지 정도로 생각하면 될 듯. 이를 두고 종종 background라고 퉁쳐서 표현하곤 한다.)
우리의 나머지 코드들은 Web APIs와는 상관없이 자바스크립트 엔진 내부에서 동작한다.
우리가 setTimeout 함수를 호출하면 Web APIs 안에 타이머와 함께 콜백 함수가 생성되는데
이 콜백 함수는 생성되고 바로 실행되는 게 아니라,
타이머가 끝날 때까지 타이머의 등에 챡 달라붙어 업혀다니게 된다. 👩👧
그러다가 타이머가 끝나면 타이머가 사라지게 되고, 그 때서야 콜백 함수가 실행되게 되는 것.
이런저런 이유로 인해, background에서 (콜백함수를 등에 업은) 타이머가 계속 흐르는 와중에도
우리의 나머지 코드들은 타이머를 기다릴 필요 없이 block되지 않고 계속 실행될 수 있는 것이다.
■ 이제 setTimeout 함수가 return 되고나면 call stack에서 setTimeout의 execution context가 사라지고
second 또한 return하게 되므로 second의 execution context도 call stack에서 빠져나가게 된다.
그리고는 맨 처음 호출되었던 first 함수의 execution context로 돌아간다.
그 다음에는 'The end'를 출력하는 log 함수의 execution context가 call stack에 쌓이고
'The end'를 출력하고 나면 그 log 함수의 execution context도 call stack을 빠져나가게 된다.
그 다음에는 first 함수도 return하고, 맨 처음 first 함수가 호출되었던 지점으로 돌아오게 된다.
■ 지금까지 코드들이 전부 동기적으로 실행되었고, 현재 background 에는
비동기적으로 실행시켰던 setTimeout 함수의 타이머👩👧가 존재하고 있다.
■ 이제 2초가 흐르고 타이머가 사라졌다고 가정해보자.
그러면 타이머의 등에 챡 달라붙어있던 콜백 함수 🙇♀️ 에게는 어떤 일이 벌어질까?
따뜻한 등을 가진 타이머를 잃은 우리의 가여운 콜백 함수는
이제 'Message Queue(메시지 큐)'로 자리를 옮겨서, execution stack이 텅 빈 후에 실행되기 위해서 기다리게 된다.
(DOM event를 다루는 경우에도 완전히 동일한 과정이 전개된다.
DOM events의 경우, '이벤트 리스너'가 콜백 함수를 등에 업은 채로
특정 event가 일어날 때까지 Web APIs에서 오매불망 기다리게 된다.
그러다가 해당 event가 발생하자마자, 콜백 함수는 메시지 큐로 옮겨져서 실행 준비를 하게 된다.)
■ 메시지 큐에 위치한 이 콜백 함수들이 실행되려면 어떻게 해야 할까?
여기서 드디어 Event Loop(이벤트 루프) 💪 가 등장하게 된다.
■ 이벤트 루프가 하는 일은, message queue와 execution stack을 계속 감시하면서,
execution stack이 비자마자 제일 먼저 들어온 콜백 함수를 execution stack으로 밀어넣는다.
■ 아까 위의 예제에서, first 함수가 return하고 first 함수의 호출부로 돌아오게 되면
이제 execution stack은 텅 빈 상태가 된다.
그리고 message queue에는 하나의 콜백 함수가 실행되기를 기다리고 있다.
그러면 이벤트 루프는, 그 콜백 함수를 가져다가 execution stack으로 밀어넣어버린다. 💪
그리고 그 콜백 함수는 execution stack으로 들어서서 실행되는 과정을 거치게 된다.
이렇게 우리의 콜백 함수🙇♀️는 타이머의 따뜻한 등 → 메시지 큐 → execution stack(call stack) 순으로
정신 없이 등 떠밀려 이동하게 된다 ㅠㅠ
■ 이벤트 루프의 역할은 간단하다.
타이머에게서 갓 벗어나, 메시지 큐로 이동하여 오들오들 떨며 실행되기를 기다리고 있는
콜백 함수의 등을 execution stack이 비자마자 힘차게 떠미는 것. 💪
(message queue에서 execution stack으로 실행 준비중인 함수를 집어넣어주는 것)
그러면 그 콜백 함수의 내부에서 'Async Hey There!'을 출력하는 log 함수가 실행되고
실행을 모두 마친 콜백 함수의 execution context가 call stack에서 빠져나가면서 모든 실행이 끝난다.
■ 만약 그 뒤에 AJAX 요청에 대한 데이터 응답이라던지, DOM 이벤트 핸들러 같은
몇 개의 콜백함수들이 더 기다리고 있다면, 이벤트 루프는 걔네가 전부 다 처리될 때까지
계속해서 들어온 순서대로 걔네들을 execution stack에다가 밀어넣어 줄 것이다.
■ 비동기에 대해서는 그냥 간단히,
'비동기 코드는 background에서 따로 실행되고,
비동기 코드가 실행되었다는 사실이 뒤에 있는 다른 코드들도 하여금
멈추고 기다리게끔 하지는 않는다' 라는 정도로 알아두면 된다.
■ 과정을 묘사하는 동안에, 그림을 함께 넣어주면 이해하기가 훨씬 쉽겠다 싶었지만
마우스로 그림을 그려서 집어넣으면 기괴한 그림들로 인해 이해하기가 더 힘들어질 것 같아서
부득이하게 넣지 못했습니다. 그래서 각자의 캐릭터들을 묘사한 이모지를..ㅎ
이미지와 함께 기억하면 더 잘 남으니깐여
반응형'Web > 자바스크립트' 카테고리의 다른 글
Promise를 이용한 비동기 자바스크립트 (0) 2021.02.21 콜백을 이용한 비동기 자바스크립트 (0) 2021.02.20 자바스크립트의 this (0) 2021.02.13 Cypress로 BDD 테스트 코드 작성하기 👍 (0) 2021.02.07 From jQuery to VanillaJS (0) 2020.12.28