라봉이의 개발 블로그

Node.js에서 async await를 사용해야 하는 이유 본문

Node.js

Node.js에서 async await를 사용해야 하는 이유

Labhong 2018. 9. 15. 13:37
반응형

자바스크립트로 소스 코드를 작성하다보면 다음과 같은 일에 직면하게 된다.

function foo(callback1) {
	callback1(function (callback2) {
		callback2(function(callback3) {
			callback3( … );	// 계속해서 탭이 늘어난다.
		});
	});
}


맞다. 바로 ‘콜백헬’이라고 불리우는 일이 발생한다.

콜백헬이 발생하면 여러가지 단점이 생긴다. 그 중 제일 좋지 않은 단점은 극악의 가독성이다. 단순히 위의 코드만 보아도 남들이 읽기 힘들어 보인다.

이런 경우를 방지하기 위해 여태 promise 패턴을 적용했었다. 하지만 promise 패턴도 단순히 콜백을 호출하는 것보다는 가독성이 좋아지지만 마찬가지로 그닥 좋지 않은 가독성을 보여준다.


먼저 promise 함수의 예제를 작성해보자.

function fooPromise(data) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log("fooPromise");
			if(data === 1000)
				reject(new Error("my error"));
			else
				resolve(data);
		}, data);
	})
}


그리고 fooPromise를 연속해서 사용해보자.

fooPromise(3000)
	.then(function (result1) {
		~~~
		return fooPromise(result1);
	})
	.then(function (result2) {
		~~~
		return fooPromise(result2);
	})
	.then(...)


맨 처음의 코드보단 훨씬 깔끔해졌다. 탭도 처음보다는 줄어들었고 정렬된 모습이다. 하지만 그래도 일반적인 탑다운 형식보다는 읽기 힘든 모습이다.


promise는 비동기를 유지하며 좋은 가독성을 유지한다.  하지만 그보다 좋은 문법이 있다. node.js 8 LTS 버전에서 정식 지원하는 문법인데, 바로 asyncawait이다.


async와 await의 최고의 장점은 node.js의 장점인 비동기를 유지하면서 동기적인 모습의 코드 스타일을 적용할 수 있다는 점이다. 따라서 코드를 읽을 때 런타임 시 내부적으로 비동기적으로 동작하지만 코드를 읽을 땐 위에서부터 아래로 쭉 읽으면 된다.


먼저 async await의 사용법을 살펴보자.

사용법은 function 앞에 async 키워드를 붙여준 뒤 그 함수 안에서 promise 함수 앞에 await를 붙여 비동기를 유지한다. 그리고 async를 붙인 function은 내부적으로 promise를 반환하게 된다는 점을 알아두자. 먼저 코드를 보자.



async function foo(data) {				// function foo 앞에 async 키워드를 붙여준다.
	let result1 = await fooPromise(data);	// 프로미스 함수 앞에 await을 붙여준다. 코드가 간결해졌다.
	let result2 = await fooPromise(result1);
	let result3 = await fooPromise(result2);
...
}

foo(3000)				// function foo 가 내부적으로 promise를 반환하는 것을 볼 수 있다. 
	.then(() => {
		console.log(“foo finish”);
	})


위의 promise 패턴보다 훨씬 매우 깔끔한 것을 볼 수 있다.

promise의 연쇄 형태도 단순히 저렇게 변수의 초기화 형태로 깔끔한 표현이 가능할 뿐만 아니라 promise와 마찬가지로 비동기로 작동한다. async/await를 안 쓸 이유가 없다.


뿐만 아니라 async/await는 기존 자바스크립트의 기존 오류 처리인 try/catch 문에서도 잘 작동한다는 것이다.

먼저 promise를 오류 처리 하기 위해선 다음과 같이 코드를 작성해야 한다.


try {

	fooPromise(data)	// 첫 번째
		.then(function (result1) {
			~~~
			return fooPromise(result1);	// 두 번째
		})
		.then(function (result2) {
			~~~
			return fooPromise(result2);	// 세 번쨰
		})
		.catch(function(err) {	// 두 번째 fooPromise, 세 번째 fooPromise에서도 발생할 수 있기 때문에 trace 하기 어렵다.
			console.log(err);
			//throw err;	=> 에러를 throw 하지 못한다.
		})
} catch(err) {
	console.log(err);
}


다음 코드에서는 프로미스 안에서 에러를  throw를 사용할 순 없고 오로지 catch를 통해서만 에러 핸들링을 할 수 있다.

promise의 catch 안의 throw는 callback function 안에 있기 때문에 throw를 했을 때 바깥의 try/catch문에 도달하지 못한다. 하지만 async/await는 다르다.


async function foo() {
	try {
		var result1 = await fooPromise(3000);
		var result2 = await fooPromise(result1);
		var result3 = await fooPromise(1000);	// 세 번째 fooPromise. 에러를 reject한다.
	}
	catch(err) {
		console.log("result: ", err);			// reject한 new Error를 받아 콘솔창에 출력
	}
}
foo();


에러가 발생하는 세 번째 fooPromise는 1000을 받고 new Error를 reject한다. reject한 new Error는 바깥의 catch 문에서 에러를 받아 콘솔 창에 출력할 수 있다.


이것이 async/await를 사용해야만 하는 중요한 특징 중 하나이다.

코드를 정말 이쁘게 쓰고 싶고 에러 처리를 원활하고 깔끔하게 하고 싶다면 node 8 LTS부터 사용 가능한 async await를 추천하는 바이다. 아니 이제는 필수라고 하고 싶다.


반응형
Comments