JavaScript

async/await은 왜 쓰는가?

oagree0123 2022. 4. 18. 15:52

이번 글에서는 Promise와 async/await의 차이에 대해서 공부해보겠습니다!


동기 / 비동기 특징

  • 동기의 (대략적인) 특징
    • 동시에 여러 작업을 수행할 수 없다.
    • 흐름을 예측하기 쉽다. 먼저 수행되고 나중에 수행되는 것들이 명확하다.
  • 비동기의 (대략적인) 특징
    • 동시에 여러 작업을 수행할 수 있다.
    • 흐름을 예측하기 어렵다. (무엇이 먼저 완료될 지 보장할 수 없다.)

Promise

Promise는 자바스크립트에서 비동기 처리에 사용되는 객체입니다.
코드는 실행 되었지만 결과를 아직 반환하지 않은 객체라고 설명할 수 있습니다.

 

Promise는 3가지 상태가 있습니다.

 

  • Pending(대기)
  • Fulfilled(이행)
  • Rejected(실패)

비동기 처리를 아직 수행하는 단계인 Pending,
비동기 처리가 완료되어 값을 반환한 상태인 Fulfilled, 

비동기 처리가 어떠한 이유에서 실패한 상태 Rejected,

 

Promise의 사용 코드

const condition = true;
const promise = new Promise((resolve, reject) => {
  if (condition) {
    resolve('resolved');
  } else {
    reject('rejected');
  }
});

promise
  .then((res) => {
    console.log(res);
  })
  .catch((error) => {
    console.error(error);
  });

위 코드를 보면, promise라는 변수에 Promise 생성자 함수를 사용하여 할당하는 것을 볼 수 있습니다.

여기서 생성자 함수는 executor 라고 불리는 특별함 함수(함수의 형태는 화살표 함수) 하나를 인자로 받습니다.

executor 함수는 resolve와 reject 라는 두 가지 인수를 받는데, 

resolve를 호출하게 된다면 비동기 작업의 성공을 의미하고, reject를 호출하면 실패를 의미합니다.

 

Promise의 동작 처리

Promise가 끝난 후 동작을 설정할 수 있는데, 이는 then메소드catch메소드 입니다.

위 코드는 성공하도록 만들었기 때문에 resolve를 호출되기 때문에, then메소드의 동작만 실행됩니다.

만약, reject가 호출되면 catch메소드의 동작만 실행됩니다.


async / await

async/await은  Promise 와 굉장히 밀접한 연관을 가지고 있는데,
executor에 몇 가지 규칙만 적용한다면 Promise 객체를 리턴하는 함수를 async/await로 변환할 수 있습니다.

 

async 

async 키워드는 함수를 선언할 때 붙여줄 수 있습니다.

  • 함수에 async 키위드를 붙이고, Promise 생성자 함수를 제거합니다.
  • resolve(value); 를 return value; 로 변경합니다.
  • reject(error); 부분을 throw new Error(); 로 수정합니다.

await

await은 Promise가 fulfilled 또는 rejected이 될 때까지 기다리는 함수입니다

 

위 코드를 수정해 보면 아래와 같습니다.

(async () => {
  const condition = true;
  const promise = new Promise((resolve, reject) => {
    if (condition) {
      resolve('resolved');
    } else {
      reject('rejected');
    }
  });

  try {
    const result = await promise;
    console.log(result);
  } catch (err) {
    console.error(err);
  }
})();

Promise를 사용했던 코드를 익명 함수에 async/await 를 사용하였습니다.
async 익명 함수의 await으로 Promise의 반환 값을 result 변수에 담아 출력하는 예제입니다.

여기서는 Promise와 다르게 try-catch()문으로 동작을 처리하는 것을 볼 수 있습니다.

 

왜 async/await을 쓸까 ?

1. async는 에러 위치를 찾기 쉽습니다. 

만약, Promise를 여러번 호출해야하는 경우가 생긴다고 한다면

어떤 then메소드에서 오류가 났는지 확인하기 어렵습니다.

function sample() {
  return sampleFunc()
    .then(data => return data)
    .then(data2 => return data2)
    .then(data3 => return data3)
    .catch(err => console.log(err))  // 결과적으로 문제가 발생했다
}

async function sample() {
  const data1 = await sampleFunc();      // 문제 발생시 data1값이 유효치 않음
  const data2 = await sampleFunc2(data1);
  return data2;
}

2. async 코드는 가독성이 좋습니다.

아래의 코드를 보면 then에 비해 async를 사용했을 때, 짧아지는 것을 볼 수 있습니다.

또한, 리턴값을 변수에 담아 사용하므로 직관적으로 변수를 인식할 수 있습니다.

function printAnimals() {
  return getAnimals()
    .then(data => {
      if (data.property) {
        return sampleFunc1(data)
          .then(anotherData => {
            console.log(anotherData)
          })
      }else {
        console.log(data)
      }
    })
}

async function printAnimals() {
  const animals = await getAnimals();
  if (animals.property) {
    const sampleData = await sampleFunc1(animals);
    console.log(sampleData);
  }else {
    console.log(animals);
  }
}

3. async는 에러 핸들링에 유리합니다.

이번에는 printAnimals()에서 에러가 발생한 게 아니라, JSON.parse에서 에러가 발생했다고 가정합니다.

이 경우에는 try-catch()문을 사용했음에도, then메소드를 사용하여 catch문을 추가로 작성해야 합니다. 

 

그러나 async를 사용하게 되면 try-catch문 하나로 처리할 뿐만 아니라,
직관적으로 어디서 오류가 났는지 파악할 수 있습니다.

function printAnimals() {
  try {
      getAnimals()
      .then((response) => {
      const data = JSON.parse(response); // 여기서 에러 발생한다고 가정
      console.log(data);
    })
    .catch((err)=> {   // 추가적인 에러
      console.log(err)
    })
  }
  catch(err) {
    console.log(err)
  }
}

async function printAnimals() {
  try {
    const data = await JSON.parse((getAnimals())
    console.log(data);
  }
  catch(err) {
    console.log(err)
  }
}