async 와 await

김민재·2021년 7월 18일
0

Gotcha JavaScript!

목록 보기
20/45

async와 awaits는 promise를 간결, 간편하게 동기적으로 실행되는 것처럼 보이게 만들어주는 것이다.
promise는 여러가지 chaning이 가능한데 promise된 다른 promise된 다른 promise된 이런식으로 chaning이 계속되면 다소 코드가 난잡해질 수 도 있다.

이런 거 위에 조금 더 간 편한 api 로 async와 awaits를 사용하면 동기 식으로 코드를 순서대로 작성하는 것처럼 간편하게 작성할 수 있다.

async와 awaits는 기존에 존재하는 promise 위에 조금 더 간편한 api를 제공하는데
이런식으로 기존에 존재하는 거 위에 또는 기존에 존재하는 것을 감싸서 더 간편하게 쓸 수 있는 api를 제공하는 것을 Syntactic sugar(ex> CLASS)라고 부른다.

async & await는 깔끔하게 promise 사용할 수 있는 방법으로 무조건 대체해서 사용하기 보단 promise를 유지해서 써야지 맞는 경우와 또는 async & await로 변환해야 더 깔끔해 지는 경우를 나누어서 살펴보아야한다.

  1. ascyn
// 1. async
function fetchUser() {
  // 사용자의 데이터를 서버로 부터 받아오는 함수
// 네트워크 통신을 해서 데이터를 받아오는 데 10초가 걸리는 코드가 있다고 가정
  return 'jamie';
}
const user = fetchUser();
console.log(user)
	

이런식으로 시간이 걸리는 코드를 비동기적인 처리를 전혀 하지 않으면 자바스크립트 엔진은 동기적 처리로 코드를 한줄 한줄 씩 수행하기 때문에 다음의 과정을 거친다.
1. fetchUser()함수가 호출 되면 함수가 선언된 것으로 간다.
2. fetchUser()함수함수에 코드 블럭을 실행한다.
3. 서버에서 네트워크 통신을해서 데이터를 받아오는데 10초가 걸려 머물게 된다.(가정)
4. 10초가 지나서 성공적으로 네트워크 데이터를 받아오면 다음 줄로 넘어가면서 jamie가 리턴된다.
5. 리턴된 코드 jamie가 user에 할당된다.
6. 마지막 라인에 jamie가가 출력된다.

이처럼 비동기적인 처리를 하지 않으면 사용자의 데이터를 받아오는 데 10초가 걸리기 때문에
만약 뒤에서 웹페이지의 ui를 표시하는 기능을 수행하는 코드들이 있다면 fetchUser()함수가 끝나는 동안 데이터가 웹 페이지에 표시되지 않기 때문에 사용자는 10초가량 비어있는 웹페이지를 보게된다.


따라서 이렇게 오래 걸리는 작업을 수행하는 경우 비동기적으로 처리할 수 있게 해줘야한다!
이때 fetchUser함수 블록 안에 내용을 promise로 만들어준다.
이는 언제 유저의 데이터를 받아올 지 모르겠지만 promise 오브젝트를 가지고 있으면 여기에 then이라는 콜백함수를 등록해 놓으면 유저의 데이터가 준비되대로 등록한 콜백 함수를 불러준다는 약속을 하는 것과 같다.

promise 안에는 resolve, reject라는 각각의 콜백함수를 받는 executer라는 콜백함수를 만들게되어 promise 코드 블록 안에 있는 것들이 비동기적으로 수행이 된다.
만약 resolve, reject를 호출하지않고 리턴을 하게되면 되면 promise가 peding 상태가 되어 있는 것을 알 수 있는데 이는 비동기 처리 로직이 아직 완료 않았음을 의미한다.

promise는 상태가 총 3가지를 가진다.

  • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태

  • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태

  • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

    따라서 꼭 promise 안에는 resolve, reject를 이용해서 완료를 해줘야한다. 이렇게 하면 결국 fetchUser는 결국 promise를 나타내기 때문에 then 콜백함수를 이용하여 유저가 들어오면 콘솔로 출력하는 것처럼 작성이 가능하다.

// 1. async
function fetchUser() {
  return new Promise((resolve, reject) => {
    resolve('jamie');
  });
}
const user = fetchUser();
user.then(console.log);

이러한 식으로 promise를 이용하지 않고 더 간편하게 비동기를 작성할 수 있는 방법이 바로
ascyn인데 함수 앞에 ascyn 키워드를 붙여기만 하면된다.
그러면 번거롭게 promise를 쓰지 않고 자동적으로 함수 안에 있는 코드 블럭들이 promise로변환이 되어진다.

// 1. async
async function fetchUser() {
   // do network reqeust in 10 secs....
  return 'jamie';
}
const user = fetchUser();
console.log(user);

ascyn 키워드는 함수에 앞에 쓰면 코드 블록 이 자동으로 promise로 바뀐다.

  1. await ✨

promise도 너무 중첩적으로 체이닝을 하게 되면 콜백지옥과 비슷한 문제점이 발생한다.

function delay(ms) {
  // delay함수는 프로미스를 리턴 하는데 정해진 시간이 지나면 resolve를 호출한다.
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function getApple() {
  // 3초가 지나면 resolve 호출
  // await 키워드를 쓰게 되면 이 딜레이가 끝날 때까지 기다려 줍니다
  // 3초가 있다가 사과를 만드는 프로미스가 만들어진다
  await delay(3000);
  return '🍎';
}
//기존의 promise 체이닝을 썼을때 
function getBanana() {
  return delay(3000)
  .then(()=>'🍌');
}

// 기존의 promise 체이닝을 썼을때 
function pickFruits() {
  return getApple().then(apple => {
    return getBanana().then(banana => {`${apple} + ${banana}`})
  })
}
pickFruits().then(console.log)

await 키워드는 ascyn가 붙은 함수 안에서만 쓸 수가 있다.
await 키워드를 쓰게 되면 함수가 끝날 때까지 기다려 줍니다.
참고 마찬가지로 에러 발생시 throw를 이용해주고 try catch문을 사용해 에러를 잡아준다.

여기서 한가지 문제는 await를 사용하면 사과를 받는데 1초를 기다리고 바나나를 받는데 1초를 기다린다. 이렇게 바나나와 애플을 받는 것처럼 서로 연관이 되어 있지 않은 것이 서로를 기다려서 순차적으로 진행하는건 비효율적이다

이러한 문제를 개선하려면
applePromise, bananaPromise를 만들어 만들자마자 promise이기 때문에 getApple(), getBanana()블록 코드를 바로 실행시킨다.
그리고 병렬적으로 사가와 바나나를 한 번에 받아서 1초를 기다렸다가 출력하게 된다.
1초만에 병렬적으로 실행이 된다.

async function pickFruits() {
  // 사과와 바나나를 모두 따오는 함수
    const applePromise = getApple(); 
    // 프로미스가 바로 실행된다.
    const bananaPromise = getBanana();
    // 이후 동기화를 시켜준다.
    const apple = await applePromise;
    const banana = await bananaPromise;
    return `${apple} + ${banana}`;
}
pickFruits().then(console.log)
  1. useful APIs ✨

이렇게 동시다발 쪽으로 수행이 가능한 경우에는요 서로 기다릴 필요없이 병렬적으로 기능을 수행할 수 있는 경우에 프로미스 에서 제공하는 유용한 api를 이용한다.

promise에 있는 all 이라는 api를 쓰면 되는데 이것은 promise에 배열을 전달하게 되면 모든 promise들이 병렬적으로 받을 때까지 모아주는 아이로,
배열 형태로 getApple(), getBanana()을 전달하게 되면 getApple(), getBanana()가 모두 다 받아지면 즉 과일의 배열이 전달받아진다.


function pickAllFruits(){
  return Promise.all([getApple(), getBanana()])
  .then(fruits => fruits.join(' + '))
}
pickAllFruits()
.then(console.log)

bonus ! 어떤 것이든 상관 없고 먼저 따지는 첫 번째 과일만 바 다하고 싶다
만약 사과는 따는데 2초가 걸리고 바나나를 따는데 1초가 걸린다면
그래서 promise에 있는 race api를 쓰면 배열에 전달된 프로미스 중에서 가장 먼저 값을 리턴하는 아이만 전달이 되어진다.

function pickOnlyOne(){
  return Promise.race([getApple(), getBanana()]);
}
pickOnlyOne().then(console.log);
profile
자기 신뢰의 힘을 믿고 실천하는 개발자가 되고자합니다.

0개의 댓글