쌓고 쌓다

[JavaScript] async, await 본문

프로그래밍/JavaScript

[JavaScript] async, await

승민아 2023. 1. 14. 17:39

비동기 처리의 필요성

function fetchUser() {
    // 10초 걸림
    return '홍길동'
}

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

사용자의 데이터를 가져오는 함수 fetchUser가 있다고 하자.

이것이 처리되는데 10초가 걸린다고 가정하면.

자바스크립트는 동기적인 처리를 하기 때문에 fetchUser가 끝날 때까지 머무르게 된다.

 

이후에 웹 페이지 UI를 표시하는 코드들이 있다면

10초가 걸리는 동안 사용자는 텅텅 빈 페이지를 보게 된다.

그래서 비동기적인 처리를 위해 promise를 사용한다.

 

Promise 적용

function fetchUser() {
    return new Promise((resolve, reject) => resolve('홍길동'))
}

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

이 promise라는 객체를 가지고 있고 then 이라는 콜백 함수를 등록해 놓으면

유저의 데이터가 준비되면 콜백함수를 불러줄게라고 약속한다.

 

async

promise를 사용하지 않고 async로도 비동기 처리가 가능하다.

함수 앞에 async라는 키워드를 붙여주면 된다.

async function fetchUser() {
    return '홍길동'
}

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

자동적으로 promise 블록들로 변환이 가능하다.

 

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

async라는 키워드로 인해 promise를 반환하는 것이 보인다.

 

await

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

이것은 프로미스가 끝날 때까지 기다린다.

 

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

async function getApple() {
    await delay(3000)
    return '사과'
}

delay에서 3000초가 지난 후 resolve를 호출하는 프로미스를 반환한다.

그것을 await를 통해 완료될 때까지(3초) 기다리는 것이다.

 

동일하게 promise를 사용하는 함수로 만들면 아래와 같다.

function getApple() {
    return delay(3000)
    .then(() => '사과')
}

 

 

1. 프로미스 또한 콜백 지옥이 발생한다.

사과와 바나나를 리턴 받는 함수가 있다고 하자.

async function getBanana() {
    await delay(3000)
    return '바나나'
}

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

pickFruits().then(console.log)

pickFruits에서 3초가 지난 후 사과를 받고 3초가 또 지난 후 바나나를 받아

사과와 바나나를 출력을 할 것입니다.

총 6초를 기다려야 하며 이 또한 콜백 지옥의 형태가 됩니다.

이것을 async 키워드로 간단히 만들 수 있다.

 

2. async로 간단히.

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

코드의 형태가 더 간단하게 보인다.

 

만약 에러가 발생한다면 어떻게 처리할까?

async function getApple() {
    await delay(3000)
    throw 'error: apple'
    return '사과'
}

getApple에서 에러가 발생했다면 try catch로 에러 처리를 한다.

 

async 에러 처리

async function pickFruits() {
    try{
    const apple = await getApple()
    const banana = await getBanana()
    } catch (error) {
        console.log(error)
    }
}

 

async 병렬 처리

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

사과와 바나나를 가져오는데 서로 연관되어 있지 않기 때문에

각각 기다리느라 총 6초가 걸리는 것은 비효율적이다.

이것을 병렬적으로 처리하여 3초가 걸리게 할 수 있다.

 

async function pickFruits() {
    const applePromise = getApple()
    const bananaPromise = getBanana()

    const apple = await applePromise
    const banana = await bananaPromise
    return `${apple} + ${banana}`
}

getApple()로 사과의 프로미스와 getBanana()로 프로미스를 만들자마자 실행되기에

병렬적으로 사과와 바나나를 동시에 실행시킨다.

 

 

사과와 바나나가 실행할 때 서로가 필요 없을 때 더 간단히, 보기 좋게 만들 수 있는 프로미스 API가 있다.

Promise.all

프로미스에 있는 all API가 있다.

프로미스 배열을 전달하면 모든 프로미스를 받을 때까지 

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

pickFruits().then(console.log)

fruits에는 다 받아진 배열이 전달된다.

 

+ Promise.race : 먼저 리턴된 프로미스를 전달하기 

async function getApple() {
    await delay(1000)
    return '사과'
}

async function getBanana() {
    await delay(3000)
    return '바나나'
}

사과는 1초, 바나나는 2초가 걸린다고 하자.

먼저 프로미스를 반환하는 것을 받아와 출력해 보자.

 

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

pickOnlyOne().then(console.log)

배열에 전달된 프로미스 중에서 먼저 값을 리턴하는 것만 전달이 된다.

 

Comments