본문 바로가기

개발/Javascript

[Javascript] Async와 Promise

최근 ES8부터 적용된 Async의 MDN 문서를 읽다가 예제로 첨부되어 있는 Promise와의 비교 코드가 정말 잘 작성되어 있는 것 같다고 생각했다. 이미 많은 사람들이 읽어 본 문서겠지만 아직 Async와 Promise가 어려운 사람들 (사실 본인)도 쉽게 이해할 수 있도록 해당 문서의 코드를 조금 수정하여 Async와 Promise의 차이, 그리고 서로 동일한 동작을 위해서는 어떻게 할 수 있는지 적어보려 한다.

 

 

1. Async

 

Async Function은 AsyncFunction 객체를 반환하는 비동기 함수이다. 

 

NodeJS와 마찬가지로 JS 역시 이벤트 루프를 통해 비동기 함수를 동작시키며, Async Function은 반드시 Promise를 반환한다.

 

먼저 Async를 사용해봤다면 Await이라는 키워드를 들어봤을 것이다.

 

Async Function과 함께 Await을 사용하면 비동기 함수를 동기적으로 실행되는 것처럼 보이게 작성할 수 있다.

 

아래 코드는 Async와 Await을 사용하여 promiseFunction1과 promiseFunction2를 순서대로 실행되도록 작성한 코드이다.

 

const promiseFunction1 = () => {
    return new Promise((resolve) => {
        console.log('Promise Function 1 START');
        resolve()
        console.log('Promise Function 1 END');
    })
}

const promiseFunction2 = () => {
    return new Promise((resolve) => {
        console.log('Promise Function 2 START');
        resolve()
        console.log('Promise Function 2 END');
    })
}

const asyncFunction = async () => {
    console.log('Async Function START');

    const resultOne = await promiseFunction1();
    const resultTwo = await promiseFunction2();
};

 

위의 코드를 실행하고, asyncFunction의 결과값을 출력한다면 어떤 실행 결과가 나올지 고민해보고 아래의 결과를 보도록 하자.

 

asyncFunction은 async로 감싸져 있는 비동기 함수이다. 그리고 해당 함수 내에서 await 키워드를 통해 프로미스를 반환하는 함수들의 실행 흐름을 제어하고 있다.

 

먼저 일반적인 함수 호출을 통해 결과값을 확인해보자.

 

asyncFunction();

// 실행 결과
Async Function Start
Promise Function 1 START
Promise Function 1 END
Promise Function 2 START
Promise Function 2 END

 

async 함수 내부에서 await을 통해 실행의 흐름을 제어했기 때문에 순차적으로 결과값이 출력되는 것을 알 수 있다.

 

하지만 await이 정말 Blocking으로 동작하는 것은 아니고, 실제로 await 또한 비동기로 동작하지만 Promise All 처럼 먼저 실행된 함수가 끝나기를 기다리는 것이다. (두 개 이상의 프로미스를 동시에 wait 하고 싶다면 Promise#then을 이용할 수 있다.)

 

그러면 asyncFunction의 값을 출력해보자.

 

console.log(asyncFunction());

// 실행 결과
Async Function Start
Promise Function 1 START
Promise Function 1 END
Promise { <pending> }
Promise Function 2 START
Promise Function 2 END

 

세번째 줄에 Promise { <pending> }이 추가되었다.

 

Promise { <pending> }은 말 그대로 "약속을 기다리는 상태" 이며, 아직 Resolved 되지 않은 상태를 의미한다.

 

async 함수 내부에서의 실행 흐름을 살펴보면 'Async Function Start'를 출력한 후 await을 통해 result에 값을 할당하고 있지만 (여기서 값은 promiseFunction이 아닌 promiseFunction()이라는 것을 잊지 말자.) Promise가 Resolved 상태가 되기 이전에 console.log()를 통해 값을 출력하고 있기 때문에 Pending이 반환되는 것이다.

 

따라서 우리가 의도하는 대로 프로미스가 Resolved 된 이후에 asyncFunction의 값을 사용하고 싶다면 다음과 같이 할 수 있다. (물론 해당 예제에서는 asyncFunction이 반환값을 주고 있지 않기 때문에 then의 첫번째 매개변수로 전달되는 결과값은 undefined이다.)

 

asyncFunction()
    .then(() => console.log('Async Function END'));

 

 

2. Promise All

 

Promise.all을 사용하면 Async를 사용하지 않고, 위와 같은 결과를 얻을 수 있다.

 

const promiseAll = () => {
    console.log('Promise All Start');

    Promise.all([promiseFunction1(), promiseFunction2()])
        .then(() => console.log('Promise All END'));
}

promiseAll();

// 실행 결과
Promise All Start
Promise Function 1 START
Promise Function 1 END
Promise Function 2 START
Promise Function 2 END
Promise All END

 

Promise.all은 배열을 통해 여러개의 프로미스를 전달할 수 있고, 결과값 또한 배열을 통해 순서대로 저장되며 await과 마찬가지로 병렬로 실행된다. 따라서 resolve()에 어떠한 값을 전달한다면 Promise.all의 then 인자로 배열로 전달한 프로미스의 순서에 따른 결과값을 가진 배열을 리턴 받을 수 있다.

 

 

 

3. Promise Chaining

 

위의 Async Await과 Promise All을 통해 코드는 동기적으로 보이지만 실제로는 병렬로 실행된다는 것을 알 수 있었다.

 

하지만 병렬이 아닌 두 개 이상의 프로미스를 동시에 wait 하고 싶다면 Promise Chaining을 사용할 수 있다. (여기서 말하는 Promise Chainig은 pyramid of doom과 같은 Promise Hell을 의미하는 것이 "절대" 아니다.)

 

const promiseChain = () => {
    console.log('Promise Chain Start');

    promiseFunction1()
        .then(() => console.log('promiseFunction1 END'));
    promiseFunction2()
        .then(() => console.log('promiseFunction2 END'));
}

promiseChain()

// 실행 결과
Promise Chain Start
Promise Function 1 START
Promise Function 1 END
Promise Function 2 START
Promise Function 2 END
promiseFunction1 END
promiseFunction2 END

 

마치며

 

간단하게 MDN 문서를 바탕으로 Async와 Promise에 대해 알아봤다.

 

프로미스는 동기적이지만 Promise Chain의 방식에 따라 실행 순서가 달라질 수 있다. (pyramid of doom...)

 

또한 Promise Chain을 사용하면 순서가 보장되지만 Promise가 Resolve 되는 시점이 setTimeout과 같은 콜백 내에 존재한다면 Promise Chain이라고 하더라도 순서가 달라질 수 있다.

 

해당 내용의 setTimeout 또한 Async Function MDN 문서에 존재하기 때문에 이벤트 루프의 개념과 함께 확인해보길 추천한다.

 


 

 

 

안녕하세요. 평범한 대학생 개발자 yorr입니다.

포스팅을 읽고 궁금한 점 또는 문의가 있으신 분은 메일 또는 댓글을 남겨주세요.

 

Mail: twysg@likelion.org

Github: https://github.com/sangyeol-kim