본문 바로가기
Develop/백엔드 & 서버

Javascript는 비동기를 어떻게 처리해 - async await

by 구운밤이다 2021. 1. 22.
728x90
반응형

시리즈 목차

이 글은 시리즈 글입니다. 이번 글에서는 자바스크립트에서 비동기 작업을 더 쉽게 다룰 수 있는 방식인 async await 에 대해 알아볼 것입니다. 기본적으로 자바스크립트가 비동기로 동작하지 않는다는 사실과 비동기룰 다루던 첫 형태인 콜백함수, 그 콜백 함수를 더 쉽게 다루기 위해 등장한 프로미스는 저번 글에서 다루었습니다! 자바스크립트가 비동기함수를 어떻게 처리하는지 동작 원리를 모르시는 분과 콜백함수, 프로미스가 뭔지 잘 모르겠다 하시는 분들은 이전 글을 참고해보세요!

Async/await

Async/await 함수를 사용하는 목적은 여러 프로미스의 동작을 동기스럽게 사용할 수 있게 해주는 것입니다. 또한 어떤 동작을 여러 프로미스 그룹에서 간단하게 동작하게 하는 것입니다. 프로미스가 구조화된 callback과 비슷한 것처럼, async/await 또한 제너레이터와 프로미스를 묶는 것과 비슷합니다. Async 함수는 항상 AsyncFunction(프로미스) 객체를 반환하는 비동기 함수인데요. 명시적으로 프로미스가 아니라면 암묵적으로 감싸지게 되죠.

예제

async function foo() {
 return 1
}

위 코드는 아래와 같습니다. 위 코드는 사람이 저렇게 async 함수를 작성하면 리턴 값을 암묵적으로 프로미스로 감싸주게 되는데, 감싸준 형태가 아래와 같은 것이죠. 즉 사람이 프로미스로 감싸주지 않으면 내부에서 저렇게 감싸진다고 생각하면 됩니다.

function foo() {
 return Promise.resolve(1)
}

사용법

Async 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수죠. 이 함수는 암시적으로 프로미스를 사용해 결과를 반환하게 됩니다. 하지만 Async 함수를 사용하면 동기함수를 사용할 때와 비슷하게 코드를 작성할 수 있습니다.

function resolveAfter2Seconds() {
 return new Promise(resolve => {
    setTimeout(() => {
         resolve('resolved');
    }, 2000);
 });
}

async function asyncCall() {
 console.log('calling');
 const result = await resolveAfter2Seconds();
 console.log(result);
 // expected output: "resolved"
}

asyncCall();

위를 보면 async 함수 내에서 await 키워드를 붙여 비동기 함수를 호출하는 것을 볼 수 있습니다. 이 await는 async 함수 내부에서만 사용할 수 있고, 밖에서 사용한다면 syntax error가 발생하게 됩니다.

예외처리

예외처리 시 이제 then과 catch 문을 쓰지 않아도 됩니다! 동기 스타일로 코드를 작성했던 때처럼 try/catach를 이용해 에러를 핸들링 할 수 있는 것이죠.

async function getFirstUser() {
    try {
        let users = await getUsers();
        return users[0].name;
    } catch (err) {
        return {
            name: 'default user'
        };
    }
}

고급 예제

이번엔 여러 비동기 함수를 하나의 async 함수안에서 호출하는 예제를 보도록 하겠습니다. 여기서는 코드를 어떻게 짜냐에 따라 속도면이나 코드의 질이 달라지게 되는데 먼저 잘못된 예시를 한번 봅시다.

var resolveAfter2Seconds = function() {
 return new Promise(resolve => {
     setTimeout(function() {
         resolve(20);
         console.log("slow promise is done");
     }, 2000);
 });
};

var resolveAfter1Second = function() {
 return new Promise(resolve => {
     setTimeout(function() {
         resolve(10);
         console.log("fast promise is done");
     }, 1000);
 });
};

var sequentialStart = async function() {
 const slow = await resolveAfter2Seconds();
 console.log(slow);
 const fast = await resolveAfter1Second();
 console.log(fast);
}

Async 함수 내에서 await을 위처럼 연속으로 호출하면 둘은 동시에 await하지 않습니다. 두 번째의 타이머가 첫번째 await에서 2초를 기다리는 동안 생성되지 않는 것이죠. 따라서 이런 식으로 await 호출을 더 하게 될 경우 비동기 함수들을 쭉 기다려가며 실행하게 되어 잘못 만든 함수라고 할 수 있습니다.

var concurrentStart = async function() {
 const slow = resolveAfter2Seconds(); // starts timer immediately
 const fast = resolveAfter1Second();
 console.log(await slow);
 console.log(await fast); // waits for slow to finish, even though fast is already done!
}

var stillConcurrent = function() {
 console.log('==CONCURRENT START with Promise.all==');
 Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then((messages) => {
    console.log(messages[0]); // slow
     console.log(messages[1]); // fast
 });
}

var parallel = function() { 
 resolveAfter2Seconds().then((message)=>console.log(message));
 resolveAfter1Second().then((message)=>console.log(message));
}

이번에는 동시에 await을 하는 예제를 보겠습니다. 먼저 Concurrent 함수를 봅시다. slow와 fast 에 함수를 담으며 두 타이머가 생성 된 뒤 await 합니다. 이때 slow 가 더 오래(2초를) 기다려야 하므로 2초 후 함수가 종료됩니다. 아래 still concurrent에서는 저번 글에서 소개했던 Promise.all로 두 개의 비동기 함수를 묶어 줍니다. 이렇게 하면 타이머를 모두 생성하고 await합니다. 앞서 살펴본 concurrent 함수와 같은 결과를 내게 됩니다. 또 이렇게 동시에 두 개 이상의 프로미스를 wait 하고 싶다면 각각 Promise.then을 사용해도 됩니다. Parallel 함수에서 이를 잘 표현해주고 있죠.

728x90
반응형

댓글