개발 기술/개발 이야기

비동기 처리, promise와 async await

by GicoMomg (Lux) 2021. 11. 15.

1. js는 동기적인 언어이다?

  • 동기적이라는 말은 작업을 순서대로 처리하는 걸 말한다.
  • 자바스크립트는 동기적인 언어지만, 때로는 비동기적으로 처리한다.
  • 만약 자바스크립트에 많은 양의 데이터를 요청하면, 응답을 받기위해 많이 대기를 해야한다.
  • 이 경우에는 데이터 응답을 받기 전까지 다른 작업을 수행할 수 없는 문제가 발생한다.
  • 그렇기 때문에 이러한 경우에는 자바스크립트는 비동기적으로 작업을 처리한다.

하지만 비동기적으로 작업을 처리하는 과정에서 문제가 발생하는데, 예시를 함께보자

  • getAnimals()는 서버에서 데이터를 요청하여 animals를 가져오는 메소드이다.
  • 만약 getAnimals()를 사용해 animals데이터를 변수에 담고 출력한다면? 어떻게 될까?
function printAnimals() {
  const animals = getAnimals();
  console.log(animals);
}
printAnimals();

  • 앞서 언급했듯이 js에서 데이터 요청과 같이 응답에 시간이 걸리는 작업에는 비동기 처리를 한다.
  • 그렇기 때문에 getAnimals()를 실행이 끝나지 않은 상태에서 다음 코드를 실행하므로 undefined가 출력된다.
function printAnimals() {
  const animals = getAnimals();  // 1. 너 오래 걸리는구나?
  console.log(animals);          // 2. 다음 코드를 먼저 처리할게!
}
printAnimals();   // 3. undefined

  • js에서 매번 데이터 응답을 비동기적으로 처리한다면, 응답 데이터를 사용할 수 없다.
  • 그렇기에 우리는 그에 대한 대안으로 promise, async await를 사용하는 것이다.

그렇다면 promise, async await는 무엇일까? 한 번 알아보자!



2. 비동기에 대처하는 promise, async await

1) promise

(1) 프로미스란?

  • 프로미스는 비동기 처리를 위해 사용되는 객체이다.
  • 프로미스를 사용하면 비동기 메소드를 동기 메소드로 처리할 수 있다.

(2) 프로미스의 3가지 상태

  • 프로미스는 총 3가지 상태가 존재하며 설명은 아래와 같다.
  • 대기(pending): 처리 대기 상태
  • 이행(fulfilled): 비동기 처리를 성공하여 결과를 반환해 준 상태
  • 거부(reject): 비동기 처리를 실패하여 오류가 발생한 상태

A. 대기 상태

  • new Promise()를 이용해 프로미스 객체를 생성한 시점이다.
new Promise();

B. 이행 상태

  • 비동기 처리가 성공하여 resolve()가 실행된 상태로, 이를 이행 상태라고 한다.
function printNumber(n) {
  return new Promise((resolve, reject) => {
    const number = n + 1;
    resolve(number);   // resolve == 비동기처리 성공!
  })
}

  • resolve()에 전달한 파라미터는 then()에서 사용할 수 있다.
printNumber(1).then((n) => console.log(n));  // 2

C. 거절 상태

  • 비동기 처리가 실패하여 reject()가 실행된 상태로, 이를 거절 상태라고 한다.
  • catch()에서 에러원인을 확인할 수 있다.
  • 아래 코드는 의도적으로 number가 3일 때 reject()를 실행했다.
function printNumber(n) {
  return new Promise((resolve, reject) => {
    const number = n + 1;
    if (number === 3) {
      let error = new Error();
      error.name = 'ValueIsThreeError';
      return reject(error);
    }
    resolve(number);
})}
printNumber(2).catch(e => console.error(e));  // error: ValueIsThreeError

(3) 프로미스 사용 예시

프로미스는 비동기 처리를 성공(resolve)할 수도, 실패(reject)할 수도 있다. 그렇다면 실제 비동기 처리를 할 때 프로미스는 어떻게 사용해야할까? 바로 then, catch를 사용하는 것이다.

  • 우리는 getApartmentInfo()를 사용해 아파트 정보를 받고, 결과를 출력하고자 한다.
  • 처리가 성공했을 때는 resolve()에 응답값을 넘기고, 실패했을 시 reject()에 에러 메시지를 넘겨준다.
function getApartmentInfo() {
  return new Promise(function(resolve, reject) {
    $.get('url주소', (response) => {
      if (response) resolve(response);               // 성공시 응답값을 넘김
      reject(new Error("InValid Request Error"));    // 실패시 에러메시지를 넘김
    })
  })
}

  • 프로미스 처리가 성공하고 난 뒤, then()에서 그 응답 결과를 사용할 수 있다.
  • 하지만 만약 프로미스 처리가 실패했다면, catch()를 사용 하여 에러 메시지를 확인할 수 있다.
getApartmentInfo()
  .then((data) => {   // 성공시 응답값을 출력
    console.log(data);
  })
  .catch((error) => { // 실패시 에러 메시지를 출력
    console.error(error);
  })



2) async await

(1) async await란?

  • async awaitpromise처럼 비동기 처리를 위해 사용된다.
  • promise를 더 쉽게 사용할 수 있는 방법이다.

(2) async await 사용법

  • 사용법은 간단하다.
  • 동기처리를 할 함수 앞에 async를, 그리고 비동기 대상 앞에 await를 붙여주면 된다.
function getData() {
  // 서버에서 데이터를 요청하는 함수
}
async function printData() {       // 1. 동기적으로 처리할거야
  const data = await getData();    // 2. getData 처리를 기다릴거야
  console.log(data);               // 3. getData처리 성공 후 실행할 코드이지!
  })
}

어? 그렇다면 에러 핸들링은 어떻게 하나요?

  • 만약 비동기 처리 중 발생하는 에러에 대처하고 싶다면 try~catch문을 사용하면 된다.
  • 아래 코드에서는 에러상황을 확인하기 위해 throw로 에러를 발생시켰다.
function getData() {
  let error = new Error();
  error.name = 'Error';
  throw error;   // throw를 사용해 에러를 발생
}

  • try에서 로직을 처리하던 중 에러가 발생하면 catch에서 에러 핸들링 할 수 있다.
async function printData() {       
  try {
    const data = await getData();
    console.log(data);
  }catch(e) {
    console.error(e);
  }
}
printData();  // Error 

(3) 비동기 처리를 동시에 할 때, promise.all

  • 아래와 같이 과일, 고기, 채소에 대한 데이터를 가져오는 메소드가 있다.
  • 이 메소드들은 서버에서 데이터를 요청하므로, 많은 대기 시간을 요한다.
  • 이 때문에 js에서는 이 메소드들을 비동기로 작업할텐데, 이를 방지하지 위해 async await를 쓸 예정이다.
  • 하지만 여기서 그치지 않고 이 메소드들을 동시에 처리하고 싶다면 어떻게 해야할까?
function getFruit() { ... };
function getMeat() { ... };
function getVegetable() { ... };

  • promise.all()을 사용하면 된다!
  • 사용방법은 아래와 같이 promise.all()에 비동기 처리할 메소드를 배열 안에 넣어주면 된다.
async function printFood() {
  const results = await promise.all([getFruit(), getMeat(), getVegetable()]);
  console.log(results);
}

  • 만약 각각의 결과를 별도의 변수에 담고 싶다면 배열을 사용하면 된다.
async function printFood() {
  const [fruit, meat, vegetable] = await promise.all([
    getFruit(),
    getMeat(),
    getVegetable(),
  ]);
  console.log(fruit, meat, vegetable);
}

(4) 제일 빨리 끝난 처리만, promise.race

  • promise.race는 가능한 빨리 끝난 하나의 결과만 반환한다.
  • 만약 가장 먼저 끝난 promise가 실패하면, 실패 결과만 반환한다.
function getFruit() { ... };     // 처리 1초 걸림
function getMeat() { ... };      // 처리 2초 걸림
function getVegetable() { ... }; // 처리 3초 걸림
async function printFood() {
  const results = await promise.race([getFruit(), getMeat(), getVegetable()]);
  console.log(results);  // getFruit 결과만 반환
}



promise에서는 then()을 사용해 비동기 처리 결과를 이용할 수 있었고, async await에서는 await 구문 다음에 처리 결과를 이용해주면 되었다. 그런데 기존에 promise가 존재했는데 async await는 어떤 장점 때문에 추가된걸까? 다음 포스팅에서는 이 둘의 차이점에 대해 알아보겠다.

반응형

댓글