비동기 Promise, async & await

Hyun·2022년 4월 6일
0

javascript

목록 보기
1/1

동기적으로 코드를 실행시키는 JS 에서 만약 데이터를 받아오는데 10초가 걸리는 코드가 있다면, 이후의 코드들을 작업이 끝날 때까지 기다려야 할 것이다.

function fetchUser(){
        //do network request in 10sec...
        return "ellie"
}

const user = fetchUser();
//동기적으로 실행되기 때문에 아래의 코드들은 
//10초동안 기다려야 한다

//... 여러 코드들...
console.log(user)

만약 이 코드 뒤에 웹페이지의 UI를 표시하는 코드가 있다면 위 작업이 끝날때까지 데이터가 들어오지 않아 빈페이지만 볼 것이다.

따라서 이것을 비동기적으로 처리함에 따라 데이터를 받아와서 보여주는 부분을 제외한 UI들을 먼저 보여줄 수 있도록 하는 비동기 작업이 필요하고, 이때 필요한 것이 Promiseasync & await 이다.

Promise

promise 객체는 네트워크 통신으로 데이터가 준비되는 대로, then함수에서 Result 값을 받을 수 있다

resolvereject 콜백함수를 사용해야만 정상적인 promise 객체를 return 할 수 있고, 따라서 promise 객체의 Result 값을 then 함수에서 받을 수 있다.

이때 return 하는 promise 객체 값의 State 는 "fulfilled" 상태이며, Result 는 콜백함수의 인자값이다.

콜백함수(resolve, reject)를 사용한 경우

function fetchUser(){
    return new Promise ((resolve, reject) => {
        //do network request in 10sec...
        resolve('ellie')
    })
}//promise 객체를 return

const user = fetchUser();
//데이터를 비동기적으로 받아온다
user.then(console.log)
//데이터가 준비되었을 때 비동기적으로 실행된다
console.log(user)//async.js: 14

만약 그냥 return 문을 사용하면 정상적인 promise 객체를 return 할 수 없고, 따라서 then 함수에서 promsie 객체의 Result 값을 받을 수 없다.

이때 return 하는 promise 객체 값의 State 는 "pending" 상태이며, Result 는 "undefined" 이다.

return 문을 사용한 경우

function fetchUser(){
    return new Promise ((resolve, reject) => {
        //do network request in 10sec...
        return "ellie"
    })
}//promise 객체를 return

const user = fetchUser();
user.then(console.log)
console.log(user)//async.js: 14

결론

promise 안에서는 resolve 나 reject 로 꼭 마무리를 해주자

async & await

async 를 이용하면 promise 를 이용하지 않고도 간단하게 비동기적으로 사용할 수 있다.

함수 앞에 async 키워드를 붙이면 자동적으로 함수 안에 있는 코드 블럭들이 promise 로 변환되어진다.

async function fetchUser(){
        //do network request in 10sec...
        return "ellie"
}

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

await 를 async 와 함께 사용한다면 비동기 작업을 동기적인 코드를 쓰는 것처럼 작성이 가능하다. await 은 async 가 붙은 함수 내에서만 사용할 수 있다.

await 는 비동기 작업 내에서 동기적인 작업을 할때 사용되며, promise chanining 과 같은 용도로 사용된다.

예시

function delay(ms){ 
    return new Promise(resolve => setTimeout(resolve, ms));
}//ms 후에 resolve를 호출하는 promise 를 리턴

async function getApple(){
    await delay(3000);//await 은 delay 함수가 끝날때까지 기다린다(동기적인 작업)
    return "★";//결과적으로 3초후에 return 됨
}

위와 같은 코드가 있다고 할때,

setTimeout 은 비동기 함수이기 때문에 delay(3000) 앞에 await 을 써주지 않으면 3초뒤에 setTimeout 의 callback 함수인 resolve가 실행이 되어도, 비동기적으로 처리되기 때문에 getApple 함수를 호출하면 바로 return 문이 실행되어 "★" 모양이 출력된다.

하지만 await 을 delay(3000) 앞에 붙인다면 비동기 작업이라도 끝날때까지 기다리기 때문에 비동기 작업을 동기적으로 작동하는 것처럼 사용할 수 있다. 따라서 setTimeout 함수가 끝나는 3초 후에 return 문이 실행되어 "★" 모양이 출력된다.

이 작업은 예전에 만들었던 MovieApp 에서 영화 정보를 불러오는데도 사용할 수 있는데 이런 느낌을 사용할 수 있다.


위 코드의 동작 순서는 console.log(data) -> conosle.log("etc...") -> data.then(console.log) 이다.

console.log(data) 는 동기적으로 바로 출력이 되지만 아직 result 값을 가져오지 못했기 때문에, promise 객체를 출력하지만 아직 Result 에 return 된 값이 없고, State 는 pending 일 것이다.

그 다음으로 역시 동기적으로 console.log("etc...") 문이 실행되고,

마지막으로 비동기로 작동하는(프로미스객체가 return 할때까지 기다리는) data.then(conosole.log) 문이 작동한다.

////
쨋든 다시 돌아와서 프로미스도 너무 중첩적으로 chaining하면 콜백지옥과 비슷한 문제점이 발생한다(pickFruits 함수).

function delay(ms){ 
    return new Promise(resolve => setTimeout(resolve, ms));
}//ms 후에 resolve를 호출하는 promise 를 리턴

async function getApple(){
    await delay(1000);//await 은 delay 함수가 끝날때까지 기다린다(동기적인 작업)
    return "★";//결과적으로 3초후에 return 됨
}

async function getBanana(){// async & await 버전(동기적인 코드를 쓰는 것처럼 작성 가능)
    await delay(1000);
    return "●"
}
/*
function getBanana2(){
    return delay(3000)
    .then(()=> "●")//promise chaining 해야한다.
}
*/	
function pickFruits(){
    return getApple().then(apple => {
        return getBanana1().then(banana => `${apple} + ${banana}`)
    })
}

pickFruits().then(console.log)//★ + ● (2초뒤)

따라서 프로미스 또한 async & await 을 이용하여 간단하게 처리해준다.
=> 동기적으로 코드를 작성하는 것처럼 쓰고 return 값도 자연스럽게 가져올 수 있어서 간편한다. 또한 에러처리도 try & catch 등의 기존의 방법을 사용할 수 있다.

async function pickFruits(){
    const apple = await getApple();
    const banana = await getBanana();
    return `${apple} + ${banana}`
}
pickFruits().then(console.log)//★ + ● (2초뒤)

여기에는 한가지 문제점이 있다.

위 코드는 바나나와 애플을 받아오는데 각각을 기다리기 때문에 총 2초가 걸리게 된다. 하지만 바나나와 애플을 가져오는데에는 서로 연관이 없기 때문에 기다릴 필요가 없다. 따라서 병렬적으로 사용할 수 있다.

async function pickFruits(){
    //프로미스를 만들면 만들어지는 즉시 promise안에 있는 코드블럭이 (병령적으로, 동시)실행된다.
    const applePromise = getApple();
    const bananaPromise = getbanana();
    //그 후 await 로 동기화 시켜주면 된다.
    const apple = await applePromise;
    const banana = await bananaPromise;
    return `${apple} + ${banana}`;
}
pickFruits().then(console.log)//★ + ● (1초뒤)

Promise.all API 를 사용할 수도 있다.

Promise.all API 로 프로미스 배열을 전달하면 프로미스를 다 받을때까지 모아준다. 이후 다 받아진 배열이 전달되고 이 배열을 여러 메서드로 사용하면 된다.

function pickAllFruits(){
    return Promise.all([getApple(),getBanana()])
    .then(fruits => fruits.join(' + '))
}
pickAllFruits().then(console.log)//★ + ● (1초뒤)
profile
better than yesterday

0개의 댓글