책 정리/이펙티브 타입스크립트

아이템 25 비동기 코드에는 콜백 대신 async 함수 사용하기

2023. 7. 26. 14:33
목차
  1. async와 타입 추론
  2. async/await 사용해야하는 이유
  3. 요약
  • 콜백 지옥의 문제점
    • 실행의 순서는 코드의 순서와 반대
    • 콜백이 중첩된 코드는 직관적으로 이해하기 어려움
    • 요청들을 병렬로 실행하거나 오류 상황을 해결하기 어려움
  • fetchURL(url1, function(response1) { fetchURL(url2, function(response2) { fetchURL(url3, function(response3) { // ... console.log(1); }); console.log(2); }); console.log(3); }); console.log(4); //출력: 4 3 2 1
  • Promise
    • 콜백 지옥을 해결하기 위해 도입 (미래에 가능해질 어떤 것)
    • 실행의 순서는 코드의 순서와 동일
    • 오류 처리, Promise.all과 같은 고급 기법 사용에 용이
  • const page1Promise = fetch(url1); page1Promise.then(response1 => { return fetch(url2); }).then(response2 => { return fetch(url3); }).then(response3 => { // ... }).catch(error => { // ... });
  • async, await
    • 각 프로미스가 처리(resolve)될 때까지 기다림
    • async 함수 내에서 await 중인 프로미스가 거절(reject)되면 예외를 던짐
      • 일반적인 try/catch 사용 가능
    • 이전 버전에서 작동할 때 타입스크립트 컴파일러는 async와 awaut가 동작하도록 변환
    • 타입스크립트는 런타임과 관계 없이 async/await 사용 가능
  • async/await 사용해야 하는 이유
    • 코드를 작성하기 쉬움
    • 타입을 추론하기 쉬움
    • Promise.all 사용 가능
    • async function fetchPages() { const [response1, response2, response3] = await Promise.all([ fetch(url1), fetch(url2), fetch(url3) ]); // ... }

async와 타입 추론

  • await와 구조 분해 할당
    • 구조 분해 할당
      • 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식
      • […array]
    function fetchPagesCB() {
      let numDone = 0;
      const responses: string[] = [];
      const done = () => {
        const [response1, response2, response3] = responses; // 구조 분해 할당
        // ...
      };
     // ...
    }
    
    • 세 가지 response 변수의 각 타입을 Response로 추론
  • Promise.race 사용 시
    • 타입 구문이 없어도 fetchWithTimeout 의 반환 타입 Promise<Response>로 추론
    • 추론이 동작하는 이유
      • Promise.race의 반환 타입은 입력 타입들의 유니온
        • Promise<Response | never>
          • never는 일반적으로 함수의 리턴 타입으로 사용 함수의 리턴 타입으로 never가 사용될 경우, 항상 오류를 출력하거나 리턴 값을 절대로 내보내지 않음을 의미
        • never(공집합)와의 유니온은 아무런 효과가 없음 → 결과가 Promise<Response>로
      • 프로미스를 사용하면 타입스크립트의 모든 타입 추론이 제대로 동작
  • function timeout(millis: number): Promise<never> { return new Promise((resolve, reject) => { setTimeout(() => reject('timeout'), millis); }); } async function fetchWithTimeout(url: string, ms: number) { return Promise.race([fetch(url), timeout(ms)]); }

async/await 사용해야하는 이유

  • 간결하고 직관적인 코드
  • async 함수는 항상 프로미스를 반환하도록 강제
    • async 화살표 함수
    const getNumber = async() => 42;  // Type is () => Promise<number>
    
    • 직접 프로미스 생성
    const getNumber = () => Promise.resolve(42);  // Type is () => Promise<number>
    
    • 함수는 항상 동기 또는 비동기로 실행되어야 하며 절대 혼용해서는 안됨.
      • 즉시 사용 가능한 값에도 프로미스를 반환하도록 하면 비동기 함수로 통일이 강제
      • 비동기 함수로 통일이 안된 경우(비추천)
        • 캐시된 경우 콜백 함수가 동기로 호출되기 때문에 fetchWithCache() 사용하기 어려움
        let requestStatus: 'loading' | 'success' | 'error';
        function getUser(userId: string) {
          fetchWithCache(`/user/${userId}`, profile => {
            requestStatus = 'success';
          });
          requestStatus = 'loading';
        }
        
        • 캐시되어 있지 않다면 success로 변경되고, 캐시되어 있다면 success되고 나서 바로 loading으로 변경됨
      • const _cache: {[url: string]: string} = {}; function fetchWithCache(url: string, callback: (text: string) => void) { if (url in _cache) { callback(_cache[url]); // 얘는 동기 } else { fetchURL(url, text => { _cache[url] = text; callback(text); }); // 얘는 비동기 } }
      • async를 사용해 통일 한 경우(추천)
        • 일관적인 동작을 강제
        • 캐시된 경우와 안되어있던 경우 모두 success로 동작함
      • const _cache: {[url: string]: string} = {}; async function fetchWithCache(url: string) { if (url in _cache) { return _cache[url]; } const response = await fetch(url); const text = await response.text(); _cache[url] = text; return text; } let requestStatus: 'loading' | 'success' | 'error'; async function getUser(userId: string) { requestStatus = 'loading'; const profile = await fetchWithCache(`/user/${userId}`); requestStatus = 'success'; }
      • 콜백이나 프로미스를 사용하면 실수로 반(half)동기 코드를 작성할 수 있지만, async를 사용하면 항상 비동기 코드를 작성한 셈
  • async함수에서 프로미스를 반환하면 또 다른 프로미스로 래핑되지 않음
    • 언제나 반환타입은 Promise<T>
  • 타입스크립트는 비동기 코드의 개념을 잡는 데 도움이 됨

요약

  1. 콜백보다는 프로미스를 사용하는게 코드 작성과 타입 추론 면에서 유리
  2. 가능하면 프로미스를 생성하기보다는 async/await를 사용
    1. 간결하고 직관적인 코드를 작성할 수 있고 모든 종류의 오류를 제거할 수 있음
  3. 어떤 함수가 프로미스를 반환한다면 async로 선언하는 것이 좋음
저작자표시 비영리 변경금지 (새창열림)

'책 정리 > 이펙티브 타입스크립트' 카테고리의 다른 글

아이템 41 any의 진화를 이해하기  (0) 2023.07.26
아이템 40 함수 안으로 타입 단언문 감추기  (0) 2023.07.26
아이템 26 타입 추론에 문맥이 어떻게 사용되는지 이해하기  (0) 2023.07.26
아이템 14 타입 연산과 제너릭 사용으로 반복 줄이기  (0) 2023.07.26
아이템 13 타입과 인터페이스의 차이점 알기  (0) 2023.07.26
  1. async와 타입 추론
  2. async/await 사용해야하는 이유
  3. 요약
'책 정리/이펙티브 타입스크립트' 카테고리의 다른 글
  • 아이템 40 함수 안으로 타입 단언문 감추기
  • 아이템 26 타입 추론에 문맥이 어떻게 사용되는지 이해하기
  • 아이템 14 타입 연산과 제너릭 사용으로 반복 줄이기
  • 아이템 13 타입과 인터페이스의 차이점 알기
Rulu_
Rulu_
벨로그가 보기 편해요!! 티스토리는 md파일 그대로 복붙해서 올립니다ㅎ https://velog.io/@hafnium1923
루루의 개발 일지 벨로그가 보기 편해요!! 티스토리는 md파일 그대로 복붙해서 올립니다ㅎ https://velog.io/@hafnium1923
Rulu_
루루의 개발 일지
Rulu_
전체
오늘
어제
  • 분류 전체보기 (61)
    • FE 기술 (28)
      • JavaScript (6)
      • React (9)
      • React Native (2)
      • Technic (7)
      • CSS (4)
    • BE 기술 (1)
    • 책 정리 (0)
      • 코어 자바스크립트 (7)
      • 이펙티브 타입스크립트 (6)
    • 우아한 테크 코스 회고 (14)
      • 프리코스 후기 (5)
      • 레벨 1 (2)
      • 레벨 2 (2)
      • 레벨 3 (5)
    • 우아한 테크 코스 피드백 (1)
      • 레벨 1 (1)
      • 레벨 2 (0)
    • 우아한 테크 코스 생활 (3)
      • 생활 이모저모 (1)
      • 글쓰기 (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 레벨인터뷰
  • 레벨로그
  • 재사용 컴포넌트
  • 5기
  • 우아한테크코스후기
  • 우아한테크코스
  • 회고
  • 테코톡준비자료
  • 테코톡
  • input 여러개
  • spread operator
  • props 여러개
  • 한달생활기
  • 프론트
  • react
  • input 컴포넌트

최근 댓글

최근 글

hELLO · Designed By 정상우.
Rulu_
아이템 25 비동기 코드에는 콜백 대신 async 함수 사용하기
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.