Javascript로 프로그래밍을 할 때 가장 골치인 것이 비동기 콜백 함수를 사용할 때이다. 비동기 함수가 유용할 때도 있지만, 크롤링 등의 작업을 할 때는 순차적으로 프로세스를 진행해야하는데 비동기 함수로 인해 코드 리딩이 어려워진다. Node.js 버전 7부터는 Async/Await 기능이 추가되어 이러한 비동기 콜백 문제를 직관적으로 해결 할 수 있게 되었다.
관련 키워드: Node.js
, Async/Await
, 비동기 콜백
, 콜백 지옥
Async / Await 문법
Async / Await 문법은 ES7에서 새롭게 지원하는 문법으로, Node.js v7
에서부터 지원된다. 현재 LTS 버전은 6 버전이지만 크롤러와 같은 단일 프로그램을 작성할 때에는 Stable 버전인 7로 사용하는 것을 추천한다. 현재 브라우저 등에서는 ECMA6는 지원 중이지만 7이 지원되려면 시간이 한참 걸릴 것 같다.
Async / Await 를 사용하면, 기존 async 라이브러리나 Promise를 사용하지 않고도 비동기 콜백 지옥을 효과적으로 해결 할 수 있다. 물론 Async/Await 를 사용하려면 Promise 함수가 사용되기 때문에 사전에 Promise 사용 방법을 이해해야한다.
Promise를 잘 모른다면 위의 글을 먼저 읽는 것을 추천한다.
Await를 위한 Promise 함수 선언
Await를 사용하기 위해서는 먼저 Promise 함수를 정의하여야한다. setTimeout 함수로 비동기 함수 2개를 만들어보자.
'use strict';
let asyncFunction1 = (message)=> new Promise((resolve)=> {
setTimeout(()=> {
console.log('fn-1', message);
resolve('fn-1');
}, 1000);
});
let asyncFunction2 = (message)=> new Promise((resolve)=> {
setTimeout(()=> {
console.log('fn-2', message);
resolve('fn-2');
}, 500);
});
Code language: JavaScript (javascript)
위의 함수는 각각 1000ms, 500ms 후에 메세지를 출력하고 결과를 리턴하는 함수이다. 먼저 기존의 방식으로 비동기 함수를 호출해보자.
function notAsyncMain () {
asyncFunction1('hello');
asyncFunction2('world');
}
notAsyncMain();
Code language: JavaScript (javascript)
위와 같이 단순 호출할 경우 결과는 아래와 같다.
fn-2 world
fn-1 hello
결과를 보면 두 함수가 병렬로 동작하여 2번째 함수가 먼저 출력을하고 1번째 함수가 출력을한다. 이 함수를 순차적으로 실행하기 위해서는 Promise를 사용하여 코드를 구성해야한다.
function notAsyncMain () {
asyncFunction1('hello').then((data)=> {
console.log(data);
return asyncFunction2('world');
}).then((data)=> {
console.log(data);
});
}
notAsyncMain();
Code language: JavaScript (javascript)
위의 코드를 실행해보면 Promise 함수의 결과를 받아서 처리 할 수 있고 순차적으로 결과가 출력되는 것을 확인 할 수 있다.
fn-1 hello
fn-1
fn-2 world
fn-2
Async 함수 선언
Promise를 사용하면 비동기 함수를 순차적으로 사용 할 수 있지만, 코드의 직관성이 떨어지고 함수가 많아지면 편집이 어려워진다. 이를 해결하기 위해서 최신 문법에서는 Async 문법을 만들어서 코드를 효율적으로 짜는 것이 가능해졌다.
async function asyncMain () {
let data = await asyncFunction1('hello');
console.log(data);
data = await asyncFunction2('world');
console.log(data);
}
asyncMain();
Code language: JavaScript (javascript)
Async 함수는 위와 같이 정의된다. await 문법은 Async 함수 안에서만 사용이 가능하고, async 내에서라고 해도 함수 안에 선언된 경우 사용이 불가하다.
async function asyncMain () {
let fn = ()=> {
let data = await asyncFunction1('hello');
return data;
};
let result = fn();
console.log(result);
}
asyncMain();
Code language: JavaScript (javascript)
예를 들면, 위와 같은 코드는 동작할 수 없다. fn 이라는 함수 안에서 await 문법은 문법적 오류가 발생하며 아래와 같은 에러 로그가 출력된다.
/Users/proin/workspace/example/await/index.js:45
let data = await asyncFunction1('hello');
^^^^^^^^^^^^^^
SyntaxError: Unexpected identifier
at createScript (vm.js:53:10)
at Object.runInThisContext (vm.js:95:10)
at Module._compile (module.js:543:28)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.runMain (module.js:605:10)
at run (bootstrap_node.js:423:7)
at startup (bootstrap_node.js:147:9)
Code language: JavaScript (javascript)
이러한 구조를 실행하기 위해서는 fn 함수 또한 async 함수로 만들어야한다.
async function asyncMain () {
async function fn () {
let data = await asyncFunction1('hello');
return data;
};
let result = await fn();
console.log(result);
}
asyncMain();
Code language: JavaScript (javascript)
async 함수 또한 생성되면 Promise 함수로 정의된다. 따라서 실행시 동기 시키려면 await를 앞에 붙여주어야한다.
결론
Promise 개념을 이해한 후 Async / Await 문법을 잘 사용하면 순차적 작업이 필요한 단일 프로그램을 효율적으로 작성할 수 있다. 기존 다른 언어의 문법과 같이 await를 사용하면 순차적이고 직관적으로 보이게 되므로 익숙한 방식으로 비동기 문제 해결이 가능하다.
하지만 Node.js 7 버전이 아니라면 사용이 안되기 때문에 현재 LTS로 제공되는 6버전에서는 사용이 안되고, 라이브러리로 만들었을 때에는 오류를 발생시키는 원인이 될 수 있다.
또한 콜백 지옥은 피할 수 있지만, 기존 콜백의 병렬 프로세싱과 같은 장점 또한 사용이 안되므로 사용 목적에 따라 유연하게 사용해야한다. (무작정 사용하게 되면 성능 저하의 원인이 될 수 있다. 예를 들면, 웹 서버를 구축할 때 비동기 함수를 await로 사용한다면, 사용자가 동시에 접속하여 병목 현상이 발생 할 때 대응하기가 더 힘들다.)