콜백 함수 시리즈


2019. 06. 08 수정

 

1. async / await

JS 프로그래밍을 하다보면 비동기 방식을 많이 사용하게 되는데요, 비동기 호출 후 이를 처리하는 콜백 함수의 개념은 매우 중요합니다.

즉, 비동기 프로그래밍에서 콜백 함수는 반드시 사용해야 하는 부분이죠.

그런데 콜백 함수가 깊어지면 코드가 복잡해지게 되므로( 가독성이 안좋아지므로 ) ES6에서 Promise를 도입했습니다.

( 콜백함수에 대한 내용은 여기를 , Promise에 대한 내용은 여기를 참고해주세요. )

 

하지만 Promise를 사용해도 여전히 코드가 복잡했는데요.

그래서 ES8에서 asyncawait을 도입했고, 덕분에 비동기 코드를 동기적으로 깔끔하게 처리할 수 있게 되었습니다.

 

그렇다고 항상 async/await이 옳다고는 할 수 없습니다.

  • 콜백의 깊이가 깊지 않을 때는 작성하기 간편한 콜백함수를 호출하거나, Promise를 사용하는 것이 더 나은 방법일 수도 있습니다.
  • async/await은 Promise를 사용하기 때문에 Promise를 알아야 하고, async/await이 할 수 없는 동작을 Promise로 해결할 수 있는 경우도 있습니다.
따라서 Promise와 async/await을 잘 이해해서 때에 따라 사용하는 것이 중요할 것 같습니다.

 

이제 비동기 코드를 깔끔하게 작성할 수 있도록 도와주는 async/await에 대해 알아보도록 하겠습니다.

 

 

 

 

2. 예시로 보는 기본문법

1) async / await 키워드

콜백함수를 사용하기 위해서는 async와 await 키워드를 사용합니다.

async function foo(){
    await someAsyncFunction(){...}
    await anotherAsyncFunction(){...}
}
  • 함수 이름 앞에 async 키워드
  • 호출할 비동기 함수 앞에 await 키워드를 사용합니다.

핵심은 await이며, async는 단지 선언용입니다.

즉, 함수 앞에 async가 선언되어 있어야만 await이 적용됩니다.

 

위의 예제는 someAsyncFunction, anotherAsyncFunction 두 함수가 비동기 코드일지라도 async / await이 적용되면,

항상 someAsyncFunction  ->  anotherAsyncFunction  순서대로 함수가 실행됩니다.

이처럼 비동키 코드를 동기적으로 수행하게 해주는 것이 async / await입니다.

이 때 async/await은 Promise 방식을 사용하기 때문에 someAsyncFunctionanotherAsyncFunction 함수는 Promise를 리턴해야 합니다.

 

이제 본격적으로 async/await이 정말로 Promise 방식을 사용하는지 살펴보면서, async/await 코딩 방법에 대해 알아보도록 하겠습니다.

 

 

 

2) 비동기 함수가 Promise를 사용하지 않는 예제 ( async / await 적용이 안된 예제 )

async function test(){
    await foo(1, 2000)
    await foo(2, 500)
    await foo(3, 1000)
}

function foo(num, sec){
    setTimeout( function(){
        console.log(num);
    }, sec);
}

test();

 

위의 예제는 비동기함수 setTimeout() 함수를 호출할 때 async/await을 사용해서 test() 함수 실행 결과 1,2,3 순서대로 응답되기를 기대한 것입니다.

그러나 1, 2, 3 순서대로 호출되지 않고, 이벤트가 끝난 순서대로 호출되었습니다.

즉, asycn/await으로 비동기 코드를 동기식으로 바꾸고 싶었지만 비동기 코드가 그대로 수행이 되었죠.

 

문법은 이게 맞는데 왜 그런걸까요?

다음 코드를 보겠습니다.

 

 

 

 

3) 비동기 함수에 Promise를 사용하는 예제 ( async / await이 적용되어 동기적으로 출력 )

async function test(){
    await foo(1, 2000)
    await foo(2, 500)
    await foo(3, 1000)
}

function foo(num, sec){
    return new Promise(function(resolve, reject){
        setTimeout( function(){
            console.log(num);
            resolve("async는 Promise방식을 사용합니다.");
        }, sec);
    });
}
test();

 

이번에는 foo() 함수가 Promise를 반환하도록 수정했습니다.

그랬더니 의도대로 비동기 함수들이 순서대로 1, 2, 3을 응답했습니다!

 

그 이유는 await은 Promise를 받기 때문입니다.

즉, 비동기 함수 foo() 실행 결과 Promise를 리턴하면 await은 이를 받아 동기적으로 수행하게 해줍니다.

 

이로써 async/await은 Promise 방식을 따른다는 것을 확인했네요.

 

 

 

 

4) test함수를 Promise로 바꾸기

이번에는 async/await과 Promise 중 어느 것이 깔끔한 표현인지(누가 더 가독성이 좋은지) 비교하기 위해 위의 예제 test() 함수를 Promise로 바꿔 표현해보도록 하겠습니다.

function test(){
    foo(1,2000)
    .then( () => {
        return foo(2,500)
    })
    .then( () => {
        return foo(3,1000)
    })
}

function foo(num, sec){
    return new Promise(function(resolve, reject){
        setTimeout( function(){
            console.log(num);
            resolve("async는 Promise방식을 사용합니다.");
        }, sec);
    });
}
test();

결과는 "3) 비동기 함수에 Promise를 사용하는 예제"와 같이 1, 2, 3 순서대로 출력됩니다.

 

코드가 복잡해질수록 async/await과 Promise 중 어느 방식이 더 가독성이 좋을까요?

사람마다 느끼는 것은 다르겠지만... 저는 async/await 방식이 더 간결해 보이네요.

 

 

 

 

5) async/await과 Promise 섞어쓰기

이번에는 async/await이 Promise를 사용한다는 점을 이용하여 Promise와 섞어서 사용해보도록 하겠습니다.

async function test(){
    await foo(1, 2000)
    await foo(2, 500).then( () => {
        foo(3, 1000)
    })
}

function foo(num, sec){
    return new Promise(function(resolve, reject){
        setTimeout( function(){
            console.log(num);
            resolve("async는 Promise방식을 사용합니다.");
        }, sec);
    });
}
test();

async/await과 Promise를 혼합해서 활용할 수 있다는 점을 보여주는 코드입니다.

그 이유는 async/await이 Promise를 반환하기 때문이죠.

 

 

 

 

3. 더 자세한 문법

앞의 예제들을 통해 async/await 사용법에 대해 알아보았습니다.

이번에는 async/await을 사용할 때 주의해야 할 문법과 표현에 대해 알아보도록 하겠습니다.

 

1) await은 aysnc함수 안에 포함되어야 합니다.

예제 - 올바르지 못한 문법 

async function test(){
    function goo(){
        await foo(1, 2000)
    }
    await foo(2, 500)
    await foo(3, 1000)
}

function foo(num, sec){
    return new Promise(function(resolve, reject){
        setTimeout( function(){
            console.log(num);
            resolve("async는 Promise방식을 사용합니다.");
        }, sec);
    });
}
test();

 

위의 예제를 실행하면 위와 같이 "await은 async 함수에서만 유효하다."는 에러가 발생합니다.

test() 함수 앞에 async를 선언했는데 무엇이 문제일까요?

 

test() 함수를 수정해보도록 하겠습니다.

 

 

 

예제 - 실행이 되긴 되는 방법 ( 올바른 결과 x )

async function test(){
    async function goo(){
        await foo(1, 2000)
    }
    goo()
    await foo(2, 500)
    await foo(3, 1000)
}

 

 

foo(1,2000) 함수의 await이 동작하기를 바라며 goo() 함수 앞에 async 키워드를 선언했습니다.

실행을 해보면 문제가 없어서 async/await이 적용된 것 같지만, 결과는 1,2,3이 순서대로 출력되지 않았습니다.

그 이유를 알아보기 위해 아래의 예제들을 더 살펴보도록 하겠습니다.

 

 

 

 

2) async 함수안에 async 함수

예제 - 실험1

// 코드 A
async function test(){
    async function goo(){
        await foo(1, 100)
    }
    goo()
    await foo(2, 500)
    await foo(3, 1000)
}

// 코드 B
async function test(){
    async function goo(){
        await foo(1, 700)
    }
    goo()
    await foo(2, 500)
    await foo(3, 1000)
}

 

 

코드 A에서 foo(1, 100) 함수를 호출했더니 순서대로 호출됐습니다.

그런데 코드 B에서 foo(1, 700)을 호출했더니 출력 순서가 또 바뀌었습니다.

 

이 예제를 통해 알 수 있는 것은 goo()함수와 나머지 비동기 함수가 경쟁을 하고 있다는 것입니다.

 

 

 

예제 - 실험2

async function test(){
    async function goo(){
        await foo(1, 700)
    }
    goo()
    await foo(2, 500)
    await foo(3, 100)
}

 

이번에는 0.5초와 0.1초로 설정하여 foo() 함수를 호출해보도록 하겠습니다.

 

여기서 눈여겨 볼 점은 goo() 함수와 나머지 비동기 함수는 경쟁을 하고 있지만, foo(2, 500) 함수와 foo(3, 100) 함수는 호출 순서를 유지하고 있다는 것입니다.

 

 

 

예제 - await으로 호출하면 해결 !

async function test(){
    async function goo(){
        await foo(1, 2000)
    }
    await goo()
    await foo(2, 500)
    await foo(3, 1000)
}

 

이제 정상적인 결과를 위해, goo() 함수에 await 키워드를 작성했습니다.

그러면 의도대로 1, 2, 3이 응답됩니다.

 

지금까지 예제들의 결론은 async 함수(test) 내부에 async 함수(goo)를 선언했을 때, goo함수 내부에 await을 선언하더라도 goo함수를 호출할 때 await을 선언해야 한다는 것입니다.

 

async/await을 사용하던 중 궁금해서 이것저것 실험했던 내용들을 주저리 주저리 설명했는데, 결론적으로는 바로 위의 코드만 기억하시면 될 것 같습니다.

 

 

 

 

3) async의 여러 표현 

async의 기본 표현은 다음과 같았습니다.

async function test(){
    await foo(1, 1000)
}

 

이를 즉시 실행 함수로도 표현할 수 있으며 다음과 같습니다.

(async function test(){
    await foo(1, 1000)
})()

 

arrow 함수로도 표현할 수 있습니다.

let test = async () => {
    await foo(1, 1000)
}

async 함수는 위와 같이 즉시 실행함수, arrow 함수로 표현이 모두 가능합니다 !

 

 

 

 

이상으로 async와 await에 대해 알아보았습니다.

비동기 함수를 다룰 때 사용하는 기법으로 3가지가 있는데요.

  1. callback 함수를 익명함수로 사용하는 방법
  2. Promise를 사용하는 방법
  3. async/await를 사용하는 방법

각각 장단점이 있으니 때에 따라 알맞게 사용하시면 좋을 것 같습니다.