Sqsung DevLog

<타입스크립트 프로그래밍> 8장: 비동기 프로그래밍, 동시성과 병렬성 정리 본문

TypeScript

<타입스크립트 프로그래밍> 8장: 비동기 프로그래밍, 동시성과 병렬성 정리

sqsung 2023. 6. 16. 15:28

8장: 비동기 프로그래밍, 동시성과 병렬성

  • 유명한 자바스크립트 엔진(V8, SpinderMonkey)들은 태스크 멀티플렉싱 기법을 영리하게 이용하여, 스레드 하나로 비동기 작업을 처리하며, 이렇게 하나의 스레드로 비동기 작업을 처리하는 이벤트 루프가 바로 자바스크립트 엔진의 표준 모델이다
  • 자바스크립트는 이벤트 루프 기반의 동시성 모델을 이용해 멀티스레드 기반 프로그래밍에서 공통적으로 나타나는 문제점을 해결한다 (동기화된 데이터 타입의 오버헤드, 뮤텍스, 세마포어 등)
  • 타입스크립트를 이용하면 비동기 작업을 추적할 수 있으며, async/await 내장 기능을 이용해 비동기 프로그래밍을 동기 프로그래밍과 비슷한 관점에서 접근할 수 있다
  • 또한, 멀티스레드 프로그램에서 엄격한 메시지 전달 프로토콜을 지정하도록 할 수 있다

8-1. 자바스크립트의 이벤트 루프

  • 아래 코드의 경우 실행 순서는 'C -> A -> B'다.

    // A. 첫 번째 setTimeout
    setTimeout(() => console.info('A'), 1);
    
    // B. 두 번째 setTimeout
    setTimeout(() => console.info('B'), 2);
    
    // C. 그냥 console.info
    console.info('C');
    💡 예제 설명
    
    1. setTimeout을 호출하면 콜백 참조와 1을 인수로 네이티브 타임아웃 API를 호출한다
    2. setTimeout을 다시 호출하면 두 번째 콜백 참조와 2를 인수로 네이티브 타임아웃 API를 다시 호출한다
    3. C를 콘솔에 출력한다
    4. 백그라운드에서 1밀리초가 지난 다음 자바스크립트 플랫폼이 태스크를 이벤트 큐에 추가하여, 첫 번째 setTimeout에서 지정한 시간이 만료되었고 콜백을 호출할 수 있음을 알린다
    5. 다시 1밀리초가 지난 다음 플랫폼이 두 번째 setTimeout의 콜백을 호출할 수 있도록 두 번째 태스크를 이벤트 큐에 추가한다
    6. 콜 스택이 비었으므로 3번 과정을 완료한 플랫폼은 이벤트 큐에 태스크가 있는지 확인한다
    7. 설정한 타임아웃이 모두 끝났고, 이벤트 큐와 콜 스택이 모두 비었다면 프로그램이 종료된다

8-2. 콜백 사용하기

  • 비동기 자바스크립트 프로그래밍의 기본 단위는 콜백이다.
  • 콜백은 평범한 함수이지만 다른 함수의 인수에 인수 형태로 전달될 뿐이다. 따라서 비동기 코드가 호출하는 콜백은 비동기로 호출됨을 알리는 전용 타입 시그니처가 없다
  • 예를 들어 Node.js의 readFile도 시그니처만 봐서는 비동기로 동작하며 readFile을 호출한 후 결과를 기다리지 않고 제어가 바로 다음 행으로 넘어간다는 사실을 알 수 없다
  • 또한, 콜백 방식은 연달아 수행되는 작업을 코드로 표현하기 어렵다는 문제도 있다(그 유명한 콜백 지옥에 빠지게 된다)

8-3. 프로미스로 정상 회복하기

  • 앞서 언급한 콜백의 단점을 Promise로 해결할 수 있다
    function appendAndReadPromise(path: string, data: string): Promise<string> {
      return appendPromise(path, data)
        .then(() => readPromise(path))
        .catch(error => console.error(error));
    }
  • 위 예제에서는 원하는 일을 완수하는 데 필요한 비동기 작업들을 직관적인 체인으로 묶었기 때문에 콜백 지옥에 빠질 일이 없다

8-4. async와 await

  • TS는 async/await을 완벽히 지원하며 아주 강력한 타입 안전성을 제공한다

  • 프로미스가 필요한 곳에는 언제든 async/await을 사용할 수 있으며, async/await은 여러 동작을 연결된 여러 동작을 쉽게 이해하 수 있게 해주고, then을 여러번 사용할 필요가 없게 해준다

    // Promise로 구현된 예제
    function getUser() {
      getUserId(18)
        .then(user => getLocation(user))
        .then(location => console.info('got location', location))
        .catch(error => console.error(error))
        .finally(() => console.info('done getting location'));
    }
    
    // async/await으로 구현된 예제
    async function getUser() {
      try {
        let user = await getUserId(18);
        let location = await getLocation(user);
    
        console.info('got location', user);
      } catch (e) {
        console.error(e);
      } finally {
        console.info('done getting location');
      }
    }

8-5. 비동기 스트림

  • 여러 개의 데이터를 각각 밀에 다른 시점에 받게 되는 경우를 설계하기 위해 가장 흔히 Node.js의 Event Emitter, Rx.js 같은 리액티브 프로그래밍 라이브러리를 이용한다
    • 두 방식의 차이는 콜백과 프로미스의 관계와 비슷하다. 이벤트는 빠르고 가벼운 반면, 리액티브 프로그래밍 라이브러리는 더 강력하며 이벤트 스트림을 조합하고 연결하는 기능을 제공한다