[JavaScript] 끝나지 않는 비동기 🤪 비동기의 꽃, async & await 알아보기

muz·2022년 1월 12일
0
post-thumbnail

async와 await?

async와 await은 promise를 좀 더 간결하고 간편하게 사용할 수 있게 해준다. 또한 비동기적으로 실행되는 것을, 동기적으로 실행되는 것처럼 보이게 만들어준다. promise도 chaining이 된다고 했는데, 이렇게되면 callback hell때처럼 코드가 또 난잡해질 수 있다.

기존에 존재하는 이러한 promise 위나, 이 promise들을 감싸서 좀 더 간편하게 쓸 수 있는 api를 제공할 수 있는데 이를 syntactic sugar이라고 한다.

🙄 syntactic sugar?
: 이는 좀 더 간편하게 쓸 수 있는 api를 제공하는 것으로, 대표적인 예로는 class가 있다. class는 Javascript의 새로운 문법이 아니라, 프로토타입을 베이스로하여 그 위에 살짝 덧붙여진 것이다.

async

비동기처리가 필요한 코드 👨🏻‍💻

네트워크 통신을 통해 사용자의 데이터를 백엔드에서 받아오는 함수가 있다고 생각해보자. 10초가 지나면 받아온 사용자의 이름을 return하고, 이를 출력하는 코드를 작성해보자.

function fetchUser() {
 	// do network request in 10 secs ...
  	return 'muz';
}

const user = fetchUser();
console.log(user);

위와같이 다소 시간이 소요되는 작업을 작성한 코드를 비동기적인 처리를 해주지않으면 JavaScript 엔진은 해당 코드를 동기적으로 수행하기 때문에, 다음 코드들은 해당 코드가 처리되기 전까지 실행되지 않는다. 만약 해당 코드 뒤에 UI와 관련된 코드들이 있다면, 데이터를 받아오기 전까지는 UI를 볼 수 없는 것이다.
때문에 비동기적인 처리를 해주어야 한다.

1. promise를 활용한 비동기적 처리

비동기적인 처리는 Promise를 이용할 수 있다.

function fetchUser() {
 	return new Promise((resolve, reject) => {
      // do network request in 10 secs ...
      resolve('muz');
    });
}

const user = fetchUser();
user.then(console.log);

이렇게 promise를 이용해서 비동기적인 코드로 만들어줄 수 있다. 하지만 promise로 바꿔주게되면 resolve인 경우, reject인 경우도 다 나누어야 하며 에러처리도 해주고 ... 약간 번거로운 일이다. 이럴 때 async를 이용하면 된다.

2. async를 이용한 비동기적 처리

함수 앞에 async라는 키워드를 작성하면, 굳이 promise를 사용하지 않아도 비동기적인 처리가 가능해진다. 이 키워드를 함수 앞에 쓰면 코트 블록이 자동으로 promise로 바뀌는 것이다.

async function fetchUser() {
 	// do network request in 10 secs ...
  	return muz;
}

const user = fetchUser();
user.then(console.log);

async 키워드를 이용하면, promise와 똑같이 then을 이용해 다음 동작을 실행할 수 있다. 이렇게 async 키워드를 이용해 보다 쉽게 비동기 처리를 할 수 있게 되었다.

a..wait! await ✋🏻

1. promise로 과일 리턴하기 🍎, 🍌

정해진 초가 지나면 promise를 return하는 delay란 함수와 2초가 지나면 사과를 return하는 함수, 2초가 지나면 바나나를 return하는 함수를 만들어보자.

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// promise를 이용해서 getApple(), getBanana() 만들기
function getApple() {
 return delay(2000)
  .then(() => '🍎');
}

function getBanana() {
 return delay(2000)
  .then(() => '🍌');
}

위의 코드에서 promise를 이용해 getApple()getBanana()를 만들었다. promise를 이용해 체이닝하는 것보다, asyncawait 키워드를 이용하면 동기적인 코드를 쓰는 것 처럼 만들면서도 코드를 보다 쉽게 이해할 수 있게된다.

2. async와 await으로 동기적인 코드처럼 보이게 하기 🧚🏻‍♂️

getApple()getBanana()를 다음과 같이 바꿔보자.

async function getApple() {
  await delay(2000); // await이란 키워드는 async가 붙은 함수안에서만 쓸 수 있다. 
  // 3초가 지나면 resolve를 호출하는 promise가 됨. 여기서 await이란 키워드를 쓰게 되면 delay가 끝날때까지 기다려준다. 
  return '🍎'; // 그러면 3초가 지나면 사과를 리턴하는 프로미스가 만들어지게 되는 것이다. 
}

async function getBanana() {
  await delay(2000);
  // throw 'error'; // 에러 처리 방법
  return '🍌';
}

await이란 키워드는 async가 붙은 함수 안에서만 사용 가능하다. 여기서 await을 이용하면 delay가 끝날때까지 기다렸다가(즉, 2초가 지나고), 각 과일을 return하는 promise와 같은 효과를 준다.

3. promise chaining ⛓

이번에는 사과와 바나나를 동시에 장바구니에 넣는 putFruits 함수를 만들어보자.

function putFruits() {
  return getApple()
  .then(apple => {
    return getBanana()
    .then(`${apple} + ${banana}`);
  });
}

putFruits().then(console.log);

위의 코드.. 약간 콜백 지옥이 떠오르지 않는가..?

promise도 너무 중첩하다보면, 콜백 지옥과 비슷한 문제점이 발생한다. 이 문제는 async 키워드를 이용해서 해결할 수 있다.

4. promise 지옥을 해결해주는 async

async function putFruits() {
  const apple = await getApple();
  const banana = await getBanana();
  return `${apple} + ${banana}`;
}
pickFruits().then(console.log);

함수 앞에 async 키워드를 작성해주고, 각 함수에 await 키워드를 붙여서 새로운 변수에 저장한다. 그리고 putFruits()는 string 문자열을 return한다. 이를 실행하면 정확히 4초 뒤 콘솔에 결과가 출력된다.

그런데 사실 사과를 가져오는 것과 바나나를 가져오는 일은 서로 연관성이 없다. 그렇기 때문에 사과를 가져오고 바나나를 가져오거나, 바나나를 가져오고 사과를 가져올 필요가 없는 것이다. 어떻게하면 각각의 과일을 바로 가져올 수 있을까? 다음과 같이 코드를 작성해보자.

5. 사과와 바나나를 동시에 가져오는 법

async function putFruits() {
  const applePromise = getApple(); 
  const bananaPromise = getBanana();
  const apple = await applePromise; /
  const banana = await bananaPromise;
  return `${apple} + ${banana}`;
} 
pickFruits().then(console.log);

위의 코드처럼 각각의 프로미스를 만들어주면 2초 뒤 콘솔에 결과가 출력된다. 어떻게 된 것일까?

🤔 promise는 생성과 동시에 실행된다고 했다.

정말 중요한 promise의 특징 덕분에 가능한 것이다. promise는 생성과 동시에 promise 안에 있는 코드 블럭이 실행되므로, 각각의 프로미스를 생성하는 순간 각각의 함수가 실행되는 것이다.

근데 또 생각해보면 이렇게 게속 프로미스를 생성하고, 또 생성하고... 더 쉬운 방법은 없을까?

Promise가 제공하는 APIs

Promise는 다양한 api를 제공한다. 그 중 Promise.allPromise.race에 대해 알아보자.

1. Promise.all

이는 5번 코드에 있는 문제를 해결해줄 수 있는 api이다. Promise.all은 promise 배열을 전달하게되면 모든 promise들이 병렬적으로 수행될 수 있게끔 해준다.

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

Promise.all의 파라미터 인자에 배열 형태를 전달한 후, 다시 배열 형태로 return 한다. 이후 이 배열을 string으로 묶기 위해 join을 사용한 것이다.

2. Promise.race

Promise.race는 Promise 배열로 전달된 것 중 가장 먼저 처리되는, 가장 먼저 값을 return 하는 것이 선택되어진다.

function pickOnlyOne() { 
  return Promise.race([getApple(), getBanana()]);
}

pickOnlyOne().then(console.log); 

이렇게 async와 await은 Promise를 좀 더 간편하게 쓸 수 있게 해주며, Promise는 보다 더 유용하게 사용할 수 있는 다양한 api를 제공한다.

profile
Life is what i make up it 💨

0개의 댓글