async, await

zzwwoonn·2022년 5월 18일
0

Java Script

목록 보기
26/29

async, await

비동기의 하이라이트 / 꽃 !! 이라고 할 수 있는 async 와 await 에 대해서 알아보자.

이전에 공부했던 promise를 보다 간결하고 간편하게, 동기적으로 실행되는 것처럼 보이게 만들어주는 API이다.

promise

Promise Chaining

promise를 여러개 체이닝하는 예제를 했었다.

fetchNumber
.then(num => num * 2)
.then(num => num * 3)
.then(num => {
  return new Promise((resolve, reject) => {
    setTimeout(()=> resolve(num - 1), 1000);
  });
})
.then(num => console.log(num));

=> 코드가 난잡해진다 (물론 콜백 지옥보다는 훨씬 낫지만!?)

이를 조금 더 간편한 API를 사용해서 동기로 동작하는 것처럼 간편하게 표현이 가능하다. => async, await를 사용하면!!

async, await는 완전히 새로운 무엇이 아니라 기존에 존재하는 promise 위에 간편한 API를 제공하는 것이다. 이와 같은 API를 syntactic sugar라고 한다. 비슷한 한 가지 예로 클래스가 있다.

자바스크립트에서 클래스는 완전히 새로운 것이 아니라 프로토타입을 베이스로 한, 그 위에 조금 살을 붙인, 그럴싸하게 보여지는 것이다.

function fetchUser() {
    // do network request in 10 secs / 코드가 있다고 가정

    return 'ellie'; // 받아온 데이터가 ellie라고 했을 때
}

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

비동기 처리를 해주지 않으면?

자바스크립트는 기본적으로 동기 방식으로 처리를 하기 때문에 fetchUser 함수가 호출되서 위에 있는 함수 코드 블럭을 수행하면 10초동안 거기서 머무르고, 10초가 지나서 데이터를 받아오면 그제서야 다음 줄로 넘어가서 return, 그다음에 user에 데이터가 저장되고 console이 찍힌다.

그럼 그 밑에 줄에 있던 웹 UI를 표시하는 코드 등의 내용은 네트워크 통신이 끝날 때까지 수행하지 못한다.

=> promise를 이용해서 비동기 처리

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

    resolve('ellie'); 
  })
}

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

만약 resolve 나 reject 를 이용하여 promise 를 완료하지 않으면
(그냥 return 을 해준다면?) pending 상태에서 계속 머무른다.

Promise는 다음 중 하나의 상태를 가진다.

  • 대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
  • 이행(fulfilled): 연산이 성공적으로 완료됨.
  • 거부(rejected): 연산이 실패함.

async

하지만 위의 코드처럼 번거롭게 Promise 객체를 만들어주고 resolve로 완료했을 때를 알리는 등의 과정 없이

async 키워드를 이용하면 함수를 비동기로 처리가 가능하다.
=> 자동으로 함수안에 코드 블럭들이 Promise로 변환이 된다.

// async & await
// clear style of using promise

// 무조건적으로 async 와 await 가 맞는 것은 아님

//1. async
async function fetchUser() {
  	// do network request in 10 secs

    return 'ellie'; 
}

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

쉽게 말하면 함수의 첫 부분 function 키워드 앞에다가 async를 놔두면 함수의 코드 블럭들이 자동으로 promise로 바뀌는 것이다.

await

async 보다 조금 더 유용한? await에 대해 알아보자.

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
// 정해진 시간(인자로 받은)이 지나면 resolve를 호출하는 promise를 리턴한다.

async function getApple() {
  // async를 썼으니까 promise를 반환하는 함수인데
  
    await delay(3000);
  	// await : 3초가 지날 때까지 기다린다.
  	// 3초가 있다가 사과를 리턴하는 promise가 만들어짐
  	// => delay 함수의 리턴이 promise니까
  
    return '🍎';
    // 사과를 리턴
}

async function getBanana() {
    await delay(3000);
  	
    return '🍌';
  
  	// 3초 있다가 바나나를 리턴하는 함수
}

await API는 async로 선언된 함수 안에서만 쓸 수 있다.

getBanana를 promise를 이용하는 방식으로 만들어보면?

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

바로 위의 코드 같이 chaining을 하는 방식보다

async function ~~ 처럼 동기적인 코드를 쓰는 것처럼 만들어주면?

"delay가 끝날 때 까지 기다려! 기다렸다가 바나나를 리턴하면 돼" 라고 하는게 되고, 이게 chaining 방식보다 훨씬 이해하기 편하고 보다 직관적이다.

그럼 굳이 굳이 async랑 await를 써야하나? 아직 꼭 필요한지 잘 모르겠는데?

바로 다음 예제를 살펴보자

실습 예제

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

pickFruits().then(console.log);

위의 코드를 보면 제일 처음 무슨 생각이 들어야 하나?
=> 콜백 지옥!!!

그래서 pickFruits도 async를 이용하면 훨씬 간단해진다.

async function pickFruits() {
    const apple = await getApple();
    const banana = await getBanana();
    
    return `${apple} + ${banana}`;
}

pickFruits().then(console.log);

자연스럽게 우리가 원래 쓰는 방식대로 쓸 수 있어 훨씬 이해가 편하고 간편하다.

만약 에러가 발생했을 경우에는?

평소에 에러 처리 해주는 것처럼 try, catch를 이용하면 된다.

await 병렬 처리

위의 코드에는 문제가 있다.

getApple() 하고 기다려,
getBanana() 하고 기다려,

이 둘은 서로 연관이 없기 때문에 서로 기다릴 필요가 전혀 없다.
그럼 둘이 차례대로 처리되고 있고 이걸 병렬로 한꺼번에 해주고 싶은 근본적인 문제는 어떻게 해결하면 되냐 ?

async function pickFruits() {
    const applePromise = getApple();
    const bananaPromise = getBanana();
    // 프로미스 객체가 생성되면서 바로 실행이 된다. (이전 시간에 했던 내용)
    // 둘 다 바로 생성하자면서 실행이 되니까 거의 동시에 3초가 흘러 만들자마자 실행

    const apple = await applePromise();
    const banana = await bananaPromise();
    // 둘 다 동시에 생성되면서 실행이 되고 
    // await : 다 되길 기다렸다가? 3초가 지나면?
    // apple, banana에 할당이 돼 
    // => 동기화 ( 병렬적으로 실행 )
    
    return `${apple} + ${banana}`;
}

pickFruits().then(console.log);

위의 코드가 가능한 이유는 바나나를 다운받는 경우에 사과를 다운받는 게 필요없고 사과를 받는 동안에 바나나가 영향을 끼치지 않기 때문이다.

하지만 위의 코드는 상당히 더럽다. 이걸 조금 더 편하게 해줄 수는 없을까?

사실상 이 내용이 비동기의 젤 마지막, 궁극적인 목적이 아닐까 싶다

useful APIs

위의 코드(사과, 바나나 병렬적으로 처리 후 출력)를 조금 더 간단하고 간편하게 바꿔보자.

// 3. useful Promise API

function pickAllFruits() {
    return Promise.all([getApple(), getBanana()])
    // 프로미스 배열을 전달했을 때 모든 프로미스가 병렬적으로 
    // 다 받을때 까지 모아주는 메서드

    .then(fruits => fruits.join(' + '));
}
pickAllFruits().then(console.log);

어떤 것인지 상관없고 첫번째로 되는거 먼저 받고 싶다!! 했을 때
사과는 2초, 바나나는 1초가 걸린다고 가정해보자
=> 젤 위의 코드에서 사과를 2초, 바나나를 1초로 고치면 된다 (3초로 되어있다)

function pickOnlyOne() {
  return Promise.race([getApple(), getBanana()]);
  // 배열의 promise 들 중에서 가장 먼저 완료된 promise 만 나온다.
}
pickOnlyOne().then(console.log);

사과가 먼저 출력이 되고 => pickOnlyOne
사과랑 바나나가 합쳐진게 출력이 된다 => pickAllFruits

0개의 댓글

관련 채용 정보