[CH03] Item 25 : 비동기 코드에는 콜백 대신 async 함수 사용하기

유지민·2023년 8월 23일
0

Effective-Typescript

목록 보기
3/5
post-thumbnail

Item 25 : 비동기 코드에는 콜백 대신 async 함수 사용하기

  • 과거 JS : 콜백 지옥을 필연적으로 마주할 수 밖에 없는 상황
    fetchURL(url1, function(response1) {
        fetchURL(url2, function(response2) {
            fetchURL(url3, function(response3) {
                //...
                console.log(1);
            })
            console.log(2);
        })
        console.log(3);
    })
    console.log(4);
  • → ES2015 : 콜백 지옥 개선을 위해 프로미스(promise) 개념 도입
    const page1Promise = fetch(url1);
    page1Promise.then(response1 => {
        return fetch(url2);
    }).then(response2 => {
        return fetch(url3);
    }).then(response3 => {
        // ...
    }).catch(error => {
        // ...
    })
  • → ES2017 : async & await 키워드 도입
    async function fetchPages() {
        const response1 = await fetch(url1);
        const response2 = await fetch(url2);
        const response3 = await fetch(url3);
    }
    await : 각각의 프로미스가 처리(resolve)될 때까지 fetchPages 함수 실행 멈춤
    • async 함수 내에서 await 중인 프로미스가 거절(reject)되면 예외 던짐
      → 일반적인 try/catch 구문 사용 가능
      async function fetchPages() {
          try {
              const response1 = await fetch(url1);
              const response2 = await fetch(url2);
              const response3 = await fetch(url3);
          } catch (e) {
              // ...
          }
      }

ES5- 버전 대상으로 할 때의 TS 컴파일러 : async & await가 동작하도록 정교한 변환 수행
TS는 런타임에 관계 없이 async & await 사용 가능

콜백보다 프로미스async/await를 사용해야 하는 이유

  • 콜백보다 프로미스가 코드 작성이 쉬움
  • 콜백보다 프로미스가 타입 추론이 쉬움

ex) 병렬로 페이지 로드하고 싶다면 Promise.all 사용해 프로미스 조합

async function fetchPages() {
    const [response1, response2, response3] = await Promise.all([
        fetch(url1), fetch(url2), fetch(url3)
    ]);
    // ... 
}

→ best case. await와 구조 분해 할당

  • TS : 세가지 response 변수 각각의 타입을 Response로 추론
function fetchPagesCB() {
    let numDone = 0;
    const response: string[] = [];
    const done = () => {
        const [response1, response2, response3] = response;
        // ...
    };
    const urls = [url1, url2, url3];
    urls.forEach((url, i) => {
        fetchURL(url, r => {
            response[i] = url;
            numDone++;
            if (numDone === urls.length) done();
        })
    })
}

: 위 코드에 오류 처리 포함 & Promise.all과 같은 일반적 코드로의 확장은 어려움

  • 입력된 프로미스 중 첫 번째가 처리될 때 완료되는 Promise.race도 타입 추론과 잘 맞음
    Promise.race 사용해 프로미스에 타임아웃을 추가하는 방법도 많이 사용됨
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 함수는 항상 프로미스를 반환하도록 강제됨
// function getNumber(): Promuse<number>
async function getNumber() {
	return 42;
}

// async 화살표 함수
const getNumber = async () => 42; // type : () => Promise<number>

// 프로미스르 직접 생성하는 방법
const getNumber = () => Promise.resolve(42); // type : () => Promise<number>

: 즉시 사용 가능한 값에도 프로미스를 반환하는 것이 이상하게 보일 수 있으나,
비동기 함수로 통일하도록 강제하는 데 도움
→ 함수는 항상 동기 또는 항상 비동기로 실행되어야 하며, 혼용해서는 안됨

요약

  • 콜백보다 프로미스를 사용하는 것이 코드 작성과 타입 추론에서 유리함
  • 프로미스보다 async & await 사용을 권장
    • 간결하고 직관적인 코드
    • 모든 종류의 오류 제거 가능
  • 어떤 함수가 프로미스를 반환할 경우 async로 선언하는 것을 권장
profile
끊임없이 도전하며 사고하는 주니어 Web 개발자 유지민입니다.

0개의 댓글