(JS) 비동기를 동기처럼 다루기 : async & await

호두파파·2021년 2월 14일
4

호두파파 JS 스터디

목록 보기
12/27

async / await

비동기 코드를 동기식으로 표현해서 간단하게 표현하기 위해 사용하는 async / await는 가장 최근에 나온 문법이다. 기존의 비동기 처리 방식인 콜백 함수와 프러미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와준다.

기본 문법

async function 함수명() {
  await 비동기_처리_메서드_명();
}

먼저 함수의 앞에 async라는 예약어를 붙인다. 그리고 나서 함수의 내부 로직 중 HTTP 통신을 하는 비동기 처리 코드 앞에 await를 붙인다.

일반적으로 await의 대상이 되는 비동기 처리 코드는 프로미스를 반환하는 API 호출 함수이다.(반드시 await가 의도한 대로 동작해야한다)

코드 살펴보기

function fetchItems() {
  return new Promise(function(resolve, reject) {
    var items = [1,2,3];
    resolve(items)
  });
}

async function logItems() {
  var resultItems = await fetchItems();
  console.log(resultItems); // [1,2,3]
}

fetchItems()함수는 프러미스 객체를 반환하는 함수이다.
fetchItems()함수를 실행하면 프러미스가 resolved되며 결과 값은
items배열이 된다.

logItmes()함수를 실행하면 fetchItems()함수의 결과값인 items배열이 resultItems 변수에 담긴다. 콘솔에는 [1, 2, 3]이 출력된다.

await를 사용하지 않았다면 데이터를 받아온 시점에 콘솔을 출력할 수 있도록 콜백함수나, .then() 등을 사용해야 했을 것이다.

async function findUser() {
  try {
    let user = await Users.findOne({}).exec();
    user.name = 'hodoo';
    user = await user.save();
    user = await Ueser.findOne({gender: 'm'}).exec();
    ...
  } catch (err) {
    console.error(err);
  }
}

await는 promise를 받아 처리하는 키워드입니다. async함수는 promise가 없으면 의미가 없습니다. 그리고 await 키워드를 사용하려면 함수가 async 함수로 선언되야 한다. async 함수는 화살표 함수로도 가능하고, 함수 표현식으로도 가능하다.

const functionExpression = async function() {
  console.log('함수 표현식');
};
const arrowFunction = async () => {
  console.log('화살표 함수');
};
const IIFE = (async () => {
  console.log('즉시 실행 함수 표현식');
})();

주의할 점은 await는 반드시 async함수 바로 안에서만 쓰여야 한다는 점이다.

async function a() {
//(function b() {
//  await Promise.resolve(true); async 함수 바로 안이 아니라서 에러. 아래와 같이 수정
  (async function b() {
    await Promise.resolve(true); // 정상 작동되도록 함수 내부에 async 위치 
  })();
}
const returnPromise = async() => {
  return 'hodoo'
};
returnPromise().then((res) => {
  console.log(res); // 'hodoo'
});

async 함수는 return 또는 throw 값이 담긴 Promise를 리턴합니다.
promise를 리턴하기 때문에 코드의 then(성공) 또는 catch(실패)여부에 따라 다시 await로 연결할 수 있다.

async fuction another() {
  try {
    let result = await returnPromise();
  } catch (err) {
    console.error(err);
  }
}

async 함수 안에 여러 await가 있을때 앞의 await가 완료된 후에야 뒤의 await가 실행된다. 동시에 실행하려면 await Promise.all([프로미스들])을 작성해야 한다.

promise가 도입되었음에도 콜백을 사용하는 것처럼, async/await 모두 프로미스나 콜백의 완벽한 대체품이 아니다. 경우에 따라 사용할 수 있는 것이다.


const promise = new Promise(function (resolve, reject) {
  // do something async here..
  fs.readFile(path, "utf-8", (err, data) => {
    if (err) {
      reject(err);
    } else {
      resolve(data);
    }
  });
});

promise.then(functuon done (date) {
  console.log("promise success!", date);
  return 1;
}).then(function handleOne (one) {
  console.log("I am one");
  return one + 1;
}).then(function handleTwo (two) {
  console.log("i am two");
}).catch(function handleError (err) {
  console.log("promise Failed!", err);
});

promise 내부 비동기 로직, done 함수, handleOne 함수, 혹은 handleTwo 함수 어디서 에러가 나더라도 handleError 함수가 호출된다. 기존 try..catch 와 유사한 이 흐름은 자바스크립트 개발자들이 친숙한 흐름의 에러 핸들링일 뿐 아니라 실패 로직과 성공 로직의 분리가 더욱 명확해질 수 있게 돕는다.

async & await 예외 처리

async & await에서 예외를 처리하는 방법은 바로 try Catch이다. 프러미스에서 에러처리를 위해 .catch()를 사용했던 것처럼 async에서는 catch {}를 사용하면 된다.

async function logToDoTitile() {
  try {
    let user = await fetchUser();
    if (user.id === 1) {
      let todo = await.fetchTodo();
      console.log(todo.title); 
    }
  } catch (error) {
    console.log(error);
  }
}

위 예시 코드는 네트워크 통신 오류뿐만 아니라 간단한 타입 오류 등의 일반적인 오류까지도 catch로 잡아낼 수 있다. 발견된 에러는 error객체에 담기기 때문에 에러의 유형에 맞게 에러 코드를 처리해주면 된다.

출처

zerocho 블로그
캡틴 판교 블로그

profile
안녕하세요 주니어 프론트엔드 개발자 양윤성입니다.

0개의 댓글