230224_TIL

reggias·2023년 2월 24일
0

I learned

목록 보기
48/62
post-thumbnail

What new did you learn?

JavaScript async, await 개념과 활용에 대해 공부함

async, await

  • promise를 간결하게, 간편하게, 동기적으로 실행되는 것처럼 보이게 만들어주는 오브젝트
  • promise는 여러가지 chaining으로 then.then.then... 계속하게 되면 코드가 난잡해질 수 있어 이를 보완해 조금 더 간편한 API로 asyncawait을 사용하면 동기식으로 코드를 순서대로 작성하는 것처럼 간편할 수 있게 도와줌
  • 새로운 무언가가 추가된 것이 아닌 기존에 존재하는 promise 위에 좀 더 간편한 API를 제공한 것(syntactic sugar)
    비슷한 예시로 클래스가 있음. 클래스는 프로토타입을 베이스로 그 위에 덧붙여진 syntactic sugar임

그러나 무조건 promise가 나쁘고 async, await을 대체해서 사용해야한다 라는 것은 아님. promise를 유지해서 써야할 때와 async로 바꿔 써야 더 깔끔해지는 경우가 있을 뿐
이런 차이점은 프로젝트를 해보면서 감을 찾아가는 것이 좋음

배경지식

Promise

function fetchUser() {
    // do network reqeust in 10 secs...
    return 'heechan'
}

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

다음과 같이 사용자의 데이터를 백엔드에서 받아오는데 10초가 걸리는 함수가 있다고 가정
이렇게 오래 걸리는 코드를 비동기적인 처리를 하지 않으면 자바스크립트 엔진은 동기적으로 수행하기 때문에 즉, 한줄이 끝나야 그 다음줄로 넘어가는 동기적인 처리를 하기 때문에

1. const user = fetchUser(); : 함수가 호출되었네?
2. function fetchUser() { return . . . } : 함수의 코드 블록 실행
3. do network reqeust in 10 secs... : 어? 10초 걸리네? 10초동안 기다려야지...
4. 10초 후 데이터를 성공적으로 받았다! 이제 return으로 넘어가야지!
5. return 된 'heechan'이 user에 할당되고 console.log로 출력됨

여기서 비동기 처리를 전혀 하지 않으면 사용자의 데이터를 받아오는데 10초가 걸리기 때문에 만약 이 뒤에 웹페이지의 UI에 받아온 데이터를 표시하는 기능을 수행하는 코드가 있다면 이게 끝나는 동안 데이터가 웹페이지에 표시되지 않기 때문에 사용자는 10초동안 비어있는 웹페이지만 보게 될 것임

이렇게 오래 걸리는 일들은 비동기적으로 처리할 수 있게 하는 것이 Promise Object.

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

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

Promise 가 말하기를 내가 언제 유저 데이터를 가져올지는 모르지만 일단 약속할게! 네가 이 Promise Object를 가지고 있고 then이라는 콜백함수만 등록해놓으면 유저데이터가 준비되는 대로 네가 등록한 콜백함수(then)를 불러줄게! 라고 한 상황이 위의 코드

Promise 내부에는 resolve, reject 콜백함수를 받는 생성자 executor 콜백함수를 만들었음. 그래서 코드 블록 안에 있는 코드가 비동기적으로 수행됨.

function fetchUser() {
    return new Promise((resolve, reject) => {
        // do network reqeust in 10 secs...
        return 'heechan'
    })
}
const user = fetchUser();
console.log(user);

> Promise {<pending>}
> [[Prototype]]: Promise
> [[PromiseState]]: "pending"
> [PromiseResult]]: undefined

만약 resolve, reject를 만들지 않고 바로 return 하면 promise pending 상태가 되어 있는 것을 볼 수 있음

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

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

> Promise {<fulfilled>: 'heechan'}
> [[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "heechan"

resolve를 호출하면 fulfilled 상태가 되며 결과값도 생김
reject일시 rejected 상태

async 사용해보기

async function fetchUser() {
    // do network reqeust in 10 secs...
    return 'heechan'
}

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

promise를 쓰지 않고 함수 앞에 async 키워드만 붙여주면 자동으로 코드 안의 블록들이 promise로 변환됨. 이것도 async가 promise를 감싸고 있는 형태이기 때문에 syntactic sugar 임

await 사용해보기

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

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

async function getBanana() {
    await delay(2000);
    return '🍌';
}
  • await 키워드는 async 키워드가 붙은 함수에서만 사용가능

    delay 함수는 promise를 return하고 정해진 ms가 지나면 resolve를 호출하는 promise를 리턴한다.

  • await를 쓰면 2초가 걸리는 delay가 끝날 때까지 기다려줌

    2초가 지나면 사과를 return 하는 promise가 만들어짐

  • getBanana()는 2초 후에 바나나를 return 하는 promise를 만드는 함수인데 이것을 promise로 굳이 바꿔보면 아래와 같음
function getBanana() {
    return delay(3000)
    .then(() => '🍌')
}

여기서 then이 어떤 값을 받았는지는 상관없고 결국 바나나를 return 함

이렇게 chaining 하는 것보다 async-await 를 써서 동기적인 코드를 쓰는 것처럼 만들게 되면 더 쉽게 이해가 됨

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

pickFruits().then(console.log);

이번에는 과일을 한꺼번에 따오는 pickFruits 함수를 기존의 promise를 써서 만들어봄.

이걸 보면 뭔가 떠오르는 것이 있지않나? 그 이름은 콜백지옥.
프로미스도 중첩적으로 체이닝을 하게 되면 콜백지옥과 비슷한 문제점이 발생

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

pickFruits().then(console.log);

그러나 async & await 를 사용하면 간단해짐

async 에러처리

async function getApple() {
    await delay(2000);
    throw 'error';
    return '🍎';
}

async function getBanana() {
    await delay(2000);
    return '🍌';
}

async function pickFruits() {
    try {
        const apple = await getApple();
        const banana = await getBanana();
        return `${apple} + ${banana}`
    } catch(error) {
        console.log('에러남')
    }
}

reject 대신 throw, then-catch 대신 try-catch를 사용

async 병렬처리

앞서 했던 코드에서 효율면에서 문제가 있는데 사과를 받는 시간이 2초, 바나나를 받는 시간이 2초가 걸려 총 4초가 소요됨. 그런데 사과와 바나나의 데이터를 받아오는 것은 둘이 서로 연관이 되어 있지 않기 때문에 서로 기다릴 필요가 없음

첫번째 방법

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

pickFruits().then(console.log);
  • getApple()과 getBanana()는 promise를 만들고나서 곧바로 병렬적으로 실행이 됨.
  • await applePromise와 await bananaPromise로 동기화를 시키고 4초가 걸리는 작업이 2초만에 병렬적으로 실행됨

두번째 방법 useful Promise APIs

1. Promise.all

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

pickAllFruits().then(console.log)

Promise.all : 프로미스 배열을 전달하게 되면 모든 프로미스들이 병렬적으로 받을 때까지 모아주는 역할을 함.

배열형태로 getApple(), getBanana() 의 프로미스의 배열을 전달하게 되면 애네들이 다 받아지면 then 으로 다 받아진 배열이 전달이 됨. 즉 과일의 배열이 전달받아진다. 배열을 문자열로 묶을 수 있는건 join이 있었음.

2. Promise.race

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

pickOnlyOne().then(console.log)

Promise.race : 예를 들어 사과와 바나나를 받아오는 시간이 각각 2초가 아니라 사과 4초, 바나나 2초가 걸린다고 가정했을 때 가장 먼저 받아지는 바나나만을 출력하는 API

정리

async, await은 promise를 좀 더 간편하게 쓸 수 있는 오브젝트이고 promise 에는 all, race 와 같은 유용한 API가 있다.

출처

드림코딩 유튜브

profile
sparkle

0개의 댓글