[JS] 이벤트 루프와 비동기 함수

싱글 스레드에서 어떻게 비동기를 사용할 수 있는지 이벤트 루프와 함께 알아보자

오늘은 JS의 비동기에 관해 설명해 보고자 한다. JS에 대해 잘 모르고 비동기에 대한 개념이 희박하던 때, 단순히 콜백함수가 비동기 함수인 줄 알았다. 하지만 단순히 콜백함수를 만든다고 해서 비동기적으로 작동하는 것은 아니다. 특정 조건의 함수만 비동기 함수가 될 수 있다.

동기와 비동기 작업에 대해 알아보고 싶다면 해당게시글을 참고하자.

조금 더 자세히 알아보자.

콜백 함수는 동기이다.

const count = (num, callback) => {
    for (let i = 0; i < 10; i++){
        console.log(num)
        num += 1
    }
    callback(num)
}


count(1, (num) =>{
    console.log(1, num)
})
console.log("============================")
count(1000, (num) => {
    console.log(1000, num)
})

count 함수는 파라미터 num에 10번 반복문을 돌면서 1을 더하는 함수이다. 그리고 for가 끝나면 콜백을 실행한다.

해당 코드를 실행하면 아래와 같은 결과가 나온다.

1
2
3
4
5
6
7
8
9
10
1 11
============================
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1000 1010

완벽하게 동기로 실행되는 것을 볼 수 있다. 그러므로 콜백함수를 만든다고 해서 비동기로 작동하지 않는다.

javascript 런타임

js에서 비동기를 이해하기 위해서는 javacript 런타임 에 대해 이해할 필요가 있다.

런타임은 어떠한 코드가 실제로 구동할 수 있는 환경을 의미힌다. 그러므로 당연하게도 javascript 런타임은 js 코드를 실제로 구동할 수 있는 환경이다.

javascript 런타임도 종류가 있는데, 크게 분리하면 웹 브라우저node.js가 있다. 웹 브라우저는 흔히 사용하는 크롬, 사파리, 파이어폭스 등이 있다.

앞서 런타임은 구동 환경을 의미한다고 했다. javascript 런타임에는 어떤 종류의 환경이 존재할까?

  • javacript 엔진
    • 이벤트 루프 포함
  • Web APIs(node.js에서는 libuv)

이 두가지가 존재한다.

javascript 엔진

그렇다면 javascript 엔진이란 무엇일까? js 코드를 실제로 실행하는 영역이다. 개발자가 작성한 모든 js 코드는 javascript 엔진에서 파싱되고 실행된다. 이 javascript 엔진싱글 스레드에서 작동한다.

여기에는 흔히 알려져 있는 이벤트 루프도 포함되어 있다. 이벤트 루프에 대해서는 뒤에서 자세하게 설명하도록 하겠다. 우선 이 이벤트 루프javascript 엔진에서 작동한다는 사실만 기억하자.

javascript 엔진의 구성 요소

  • 콜 스택 (call stack)
    • 실행되어야할 함수들을 저장하는 곳

    • const test = (num) => { console.log(num) }
      
      test(1)
      

      JS 콜스택

      • test(1)이 이 콜스택에 삽입 후 실행
      • test(1)에 있는 console.log(1)을 콜스택에 삽입 후 실행
      • 1이 출력 후 console.log(1) 콜스택에서 제거
      • test(1) 콜스택에서 제거
  • 힙 (heap)
    • 데이터가 동적으로 저장되는 공간
  • 이벤트 루프(event loop)
    • 비동기 작업이 끝난 후 결과값을 처리할 콜백 함수를 저장해두는 곳

Web APIs

웹 브라우저에서 비동기적인 이벤트를 처리하기 위해 제공하는 API들이다. 웹 브라우저에서 제공하는 것이기 때문에, 해당 api는 javascript 엔진에서 실행되지 않는다. 이 api들은 javascript 엔진과 실제로 다른 프로세스에서 실행되며, js에서 호출할 수 있도록 javascript 엔진에서는 인터페이스만 제공한다.

여기서 잠깐! 인터페이스만 제공한다는 것의 의미란?

API는 Application Programming Interface의 약자이다. 이는 두 개의 서로 다른 프로세스(혹은 프로그램)간에 통신을 위한 일종의 약속이다.

AB라는 두 개의 프로세스가 있고, B에는 getData라는 기능이 있다. A에서 BgetData라는 기능을 사용하기 위해서는 getData의 실행에 필요한 데이터를 전달해 주어야 한다. 이때 "B는 특정 형태의 프로토콜과 특정 형태의 구조로 데이터를 전달해주면, getData라는 기능을 실행하고, 결과를 특정한 형태로 응답해주겠다" 라고 약속하는 것이 바로 API라고 할 수 있다.

Web APIs도 똑같다. 실제 Web APIs는 js 코드로 실행하면, javascript 엔진이 해당 요청을 다른 프로세스(=웹 브라우저)에게 요청하고, 응답을 받으면 그것을 js에서 사용할 수 있도록 결과값을 반환해주는 역할을 한다.

즉, javascript 엔진웹 브라우저 간의 통신 약속이 바로 Web APIs이다.

해당 게시글을 처음 시작할 때, 특정 조건의 함수만 비동기가 될 수 있다고 했다. 그 특정 조건의 함수란 바로, Web APIs에 등록된 함수이다.

Web APIs 함수 목록-mdn web docs

이벤트 루프

이벤트 루프를 이해하기 위해서는 비동기에 대한 이해가 필요하다.

비동기란? 파일 저장, 네트워크 요청 등 처리에 시간이 걸리는 I/O 작업이 진행 중에 다른 코드를 실행하는 것을 의미한다. 시간이 걸리는 작업 중에는 프로그램은 놀고 있는 것이기 때문에, 이 시간을 최대한 활용하기 위해 나온 개념이다.

js는 싱글 스레드이기 때문에, 비동기 코드를 실행할 수 없다. 그래서 앞서 설명한 Web APIs를 통해 브라우저에게 비동기 작업을 요청한다. 그렇다면, 비동기 작업이 종료되었을때, 응답을 받을 것이다. 그 응답은 어떻게 처리해야 할까?

setTimeout(()=>{console.log(1)}, 1000)
console.log(2)
/*
결과

2
1
*/

위와 같은 코드를 보자. 해당 코드는 실행하면 2가 출력되고 1초 후 1이 출력될 것이다. 조금 이상하지 않은가? 코드는 위에서 아래로 진행하면서 실행될 텐데, 1, 2가 출력되는 것이 아니라 2, 1로 반대로 출력된다. 마치 이것은 두 번째 줄이 먼저 실행되고, 첫 번째 줄이 실행된 것 처럼 보인다.

이것이 앞서 설명한 응답 처리에 대한 질문의 답이라고 할 수 있다. 코드 실행을 순서대로 알아보자

  • setTimeout 콜스택에 삽입 후 실행
  • 1초 타이머를 Web API를 통해 웹 브라우저에게 요청, 이때 ()=>{console.log(1)}을 같이 웹 브라우저에게 전달
  • setTimeout 콜스택에서 제거
  • console.log(2) 콜스택에 삽입 후 실행
  • 2 출력
  • console.log(2) 콜스택에서 제거
  • 1초가 지난 후 웹 브라우저()=>{console.log(1)}이벤트 루프 큐에 삽입
  • 이벤트 루프는 큐에 ()=>{console.log(1)}가 생겼으므로, 해당 함수를 콜스택에 삽입
  • ()=>{console.log(1)} 실행
  • console.log(1) 콜스택에 삽입 후 실행
  • 1 출력
  • console.log(1) 콜스택에서 제거
  • ()=>{console.log(1)} 콜스택에서 제거
  • 종료

이렇듯, 코드는 순차적으로 실행된 것이 맞고, 응답 처리의 방법은 비동기 작업이 종료된 이후 처리할 코드를 미리 넘겨주는 것이다. 이것이 바로 콜백 함수이다. 위 예제에서 콜백 함수는 ()=>{console.log(1)}이다.

그리고 이벤트 루프의 역할은 비동기 처리가 완료된 이후 실행되어야 할 콜백 함수들의 저장소이다. 이 이벤트 루프는 콜백 함수를 저장하고, 일정 주기(틱)마다 콜백 함수 큐를 확인해서 콜백 함수가 존재하면 해당 함수를 콜 스택에 삽입한다.

아래 GIF를 보면 콜 스택, 웹 브라우저, 이벤트 루프간의 상호작용을 한눈에 볼 수 있다.

JS 비동기 작업 GIF

정리

  • 콜백을 사용한다고 모두 비동기가 되는 것이 아니다.
  • javascript 런타임은 js를 실행하는 구동 환경이다.
    • javascript 엔진Web APIs로 구성된다.
  • javascript 엔진은 js를 파싱하고 실행한다.
    • 싱글 스레드로 작동한다.
    • 여기서는 비동기가 작동할 수 없다.
    • 콜 스택, , 이벤트 루프로 구성된다.
  • Web APIs웹 브라우저에서 비동기 이벤트를 처리하기 위해 제공되는 api이다.
    • javascript 엔진은 비동기 요청을 Web APIs를 통해 웹 브라우저에게 전달한다.
    • 웹 브라우저는 비동기 작업이 끝나면 콜백 함수를 이벤트 루프 콜백 함수 큐에 삽입힌다.
  • 콜백 함수란 비동기 요청이 끝난 후 응답을 처리하는 함수이다.
  • 이벤트 루프란 콜백 함수 저장소이다.
    • 일정 주기(틱)마다 콜백 함수 큐를 검사하고, 콜백 함수가 존재하면 콜 스택에 해당 함수를 삽입한다.
Last updated on 2024-01-13 AM 12:23