TIL 23 | async, await

Saemsol Yoo·2020년 12월 26일
0

javascript

목록 보기
14/25
post-thumbnail

https://youtu.be/aoQSOZfz3vQ
드림코딩 by 엘리 님의 유튜브 강의를 보며 정리한 내용입니다.


async, await

비동기의 하이라이트! 비동기의 핵심! 비동기의 꽃 🌷✨
asyncawait 은 이전에 살펴본 promise 를 더 간결하고, 간편하게! 그리고 동기적으로 실행되는 것처럼 보이게 👀 만들어준다.
promise 들은 여러가지 chaining을 할 수 있는데, (promise .then에 또 다른 promise .then ...) 이런식으로 chaining을 계속 계속하다 보면 코드가 난잡해질 수가 있다. 그래서 이런 것 위에 조금 더 간편한 APIasyncawait 을 사용하면 우리가 그냥 동기식으로 코드를 순서대로 작성하는 것처럼 간편하게 작성할 수 있도록 도와준다.

🤚🏻 async와 await은 새로운 것이 추가된 게 아니라 기존에 존재하는 promise위에 조금 더 간편한 API를 제공하는 것이다. 이렇게 기존에 존재하는 것 위에 또는 기존에 존재하는 것을 감싸서 우리가 좀 더 간편하게 쓸 수 있는 API를 제공하는 것을 syntactic sugar 라고 한다. (같은 예로 class가 있다. JavaScript 에서 class는 전혀 새로운 것이 아니라 프로토타입을 베이스로 한, 그 위에 살짝 덧붙여진 syntactic sugar이다.)


clear style of using promise ✨🙂
async와 await은 깔끔하게 promise를 사용할 수 있는 방법인데, 그렇다고 무조건 promise가 나쁘고 async와 await으로 대체해서 사용해야 된다는 것은 절대 아니다!
promise를 유지해서 써야 맞는 경우가 있고, 또는 async와 await으로 변환해야지 조금 더 깔끔해지는 경우가 있다. 이런 차이점은 계속 코딩을 해나가면서 감을 찾아가자 👌🏻



1. async

1-1. 비동기처리를 하지 않는다면

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

가정 : fetchUser은 사용자의 데이터를 backend에서 받아오는 함수이다. 네트워크 통신을 해서 백엔드로부터 데이터를 받아오는 데 한 10초정도 걸리는 코드가 있다고 가정하고, 그래서 10초가 지나면 우리가 받아온 사용자의 이름을 return 한다.

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

→ 이렇게 무언가 오래 걸리는 코드를 비동기적인 처리를 전혀 하지 않으면 자바스크립트 엔진은 동기적으로 코드를 수행하기 때문에(즉, 한 줄 한 줄씩! 한 줄이 끝나야 그 다음 줄로 넘어가는,,) 그래서 "fetchUser가 호출이 됐네?" → 함수가 선언된 곳으로 가서 fetchUser 함수의 코드블럭을 실행 → 그래서 한 줄이 수행되면서 꼭 10초가 걸리니 10초 동안 거기에 머무르고 있다가 10초가 지나서 성공적으로 네트워크 데이터를 받아오게 되면 → 그제서야 다음 줄로 넘어가면서 'eden'이 return된다. → 그리고 이 return된 코드가 user에 할당이 되고 → 그다음 라인으로 넘가서 → eden이 출력된다.

🤚🏻 우리가 여기서 비동기적인 처리를 전혀 하지 않으면 사용자가 데이터를 받아오는 데 10초가 걸리기 때문에 만약 이 뒤에 웹페이지의 UI를 표시하는 기능을 수행하는 코드들이 있다면, 이것이 끝나는 동안 데이터가 웹페이지에 표시되지 않기 때문에 사용자는 10초 정도 텅텅 빈 웹페이지만 보게 될 것이다. 그래서 이렇게 오래 걸리는 일들은 비동기적으로 처리할 수 있게 해주어야 한다!! ✨


1-2. promise로 처리하기

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

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

1-3. async로 처리하기

promise를 이용하지 않고도 간편하게 비동기를 작성할 수 있는 방법이다.

  • 함수 앞에 async 라는 키워드를 붙여준다!
    (그럼 번거롭게 promise를 쓰지 않아도 자동적으로 함수 안에 있는 코드블럭들이 promise로 변환이 된다.)
async function fetchUser() {
  //do network request in 10 secs...
  return 'eden';
}

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



2. await

2-1. await 사용하기

await 이라는 키워드는 async 가 붙은 함수 안에서만 사용이 가능하다!

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

async function getApple() {
  await delay(3000); //3초 전달
  return "🍎";  
}

async function getBanana() {
  await delay(3000);
  return "🍌";
}

→ delay 라는 함수는 promise를 return하는데, 정해진 ms(밀리세컨)이 지나면 resolve를 호출하는 그런 프로미스를 리턴한다. 그래서 3초를 전달했기 때문에 → 3초가 지나면 resolve를 호출하는 프로미스가 전달이 되는 것이다.

  • getApple함수 코드블럭 안에서 await이라는 키워드를 쓰게되면 delay가 끝날때까지 기다려준다. 그럼 3초 후 🍎를 리턴하는 프로미스가 만들어진다.

바나나 함수를 동일하게 promise를 쓰는 함수로 만들어 보자면

function getBanana() {
  return delay(3000) //우선 먼저 3초가 된 다음에
  .then(() => '🍌'); //어떤 값을 받았는지는 상관없고 결국에는 🍌를 리턴한다.
}

✨ // 이렇게 chaining을 하는 것보다 async, await을 사용해 동기적 코드를 쓰는 것처럼 만들게 되면 더 쉽게 이해할 수 있다.



2-2. promise chaining을 async,await으로 바꿔주기

getApple와 getBanana를 한번에 다 처리하는 함수를 promise chaining으로 이용해 만들어보자!

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

async function getApple() {
  await delay(3000); //3초 전달
  return "🍎";  
}

async function getBanana() {
  await delay(3000);
  return "🍌";
}
//promise chaining 으로 작성
function pickFruits() {
  return getApple().then((apple) => {
    return getBanana().then((banana) => `${apple} + ${banana}`);
  });  
}  //  ..looks like callback Hell 😅

pickFruits().then(console.log); //🍎 + 🍌

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

그럼 이것을 async 라는 키워드를 이용해서 간단하게 바꿔보자!

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

async function getApple() {
  await delay(3000); //3초 전달
  return "🍎";  
}

async function getBanana() {
  await delay(3000);
  return "🍌";
}
// 이렇게 간단하게 바꿔줄 수 있다!
async function pickFruits() {
  const apple = await getApple();    //받아오는동안 3초 걸리고
  const banana = await getBanana();  //받아오는동안 얘도 3초걸린다.
  return `${apple} + ${banana}`;
}

pickFruits().then(console.log); //(6초가 지난 후..) 🍎 + 🍌

→ async와 await으로 코드가 깔끔하게 바뀌었지만 사과를 받는 데 3초가 걸리고, 바나나를 받는 데 3초가 걸린다. 그래서 총 6초가 소요가 되는데 이건 비효율적이다. 이 경우에서는 사과와 바나나를 받아오는 데 서로 연관이 되어있지 않기 때문에 서로를 기다릴 필요가 전혀 없다. 그렇기 때문에 await을 병렬적으로 처리해주는 방법을 사용해주면 좋다!



2-3. await 병렬처리

async function pickFruits() {
  const applePromise = getApple();    
  const bananaPromise = getBanana();
  //이렇게 하면 promise를 만드는 순간 바로 promise안에 들어있는 코드블럭이 실행이 된다. 
  
  const apple = await applePromise;
  const banana = await bananaPromise;
  //그리고 여기서 실행된 것들을 동기화 시켜주면
  
  return `${apple} + ${banana}`;
}

pickFruits()<.then(console.log); //🍎 + 🍌   ... 3초만에 병렬적으로 실행이 된다!

🤚🏻 하지만 여기서 또! 사과를 따는 데 바나나가 필요없고, 바나나를 따는데 사과가 필요없어서 병렬적으로 기능을 수행할 수 있는 경우에는 이렇게 코드를 작성하지 않는다. 왜냐하면 promise에서 제공하는 아주 유용한 API 가 있기 때문이다!

  • Promise.all( )
  • Promise.race( )



3. useful Promise APIs

3-1. Promise.all()

promise 배열을 전달하게 되면 모든 promise들이 병렬적으로 다 받을 때까지 모아주는 기능을 한다.
→ 얘네들이 다 받아지게 되면 그러면(.then) 받아진 배열이 다시 전달이 된다.

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

pickAllFruits().then(console.log); //🍎 + 🍌

3-2. Promise.race()

어떤것이든 상관없이! 먼저 받아지는 첫번째 과일만 받겠다!

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

async function getApple() {
  await delay(2000);
  return "🍎";  
}

async function getBanana() {
  await delay(1000);
  return "🍌";
}

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

pickOnlyOne.then(console.log); //🍌
profile
Becoming a front-end developer 🌱

0개의 댓글