비동기 - Promise / async & await

chaticker·2022년 4월 18일
0

Javascript

목록 보기
1/1
post-thumbnail

평소 많이 사용했지만 지금 생각하기에 정확한(?) 개념이 잡히지 않은 듯해서 다시 정리해 볼 겸 글을 작성한다. 추가로, Promise와 async & await에 대해 각각 어떤 경우에 사용하는 것이 효율적인지 알아보고자 한다.


Promise
callback의 단점인 무한 콜백 함수 실행(콜백 지옥)을 해결하기 위해 나온 문법

async & await
promise를 보다 간결하고, 동기적으로 보일 수 있도록 하기 위해 나온 문법

다음 예시를 살펴보자.

function fetchUser() {
	// 서버 api 통신 후 10초 후에 데이터 받아오는 코드 ...
    return userName;
}

const user = fetchUser(); // fetchUser를 통해 값을 받아 오기
console.log(user); // 콘솔에 출력

만약 위처럼 오래 걸리는 처리에 대해 비동기적인 처리를 하지 않으면, 자바스트립트 엔진은 기본이 동기적으로 코드를 수행하기 때문에, fetchUser에서 10초간 머무르고 있게 된다.

그 후 만일 UI를 처리하는 코드가 위 코드 블럭 다음에 위치하게 되면, 끝나는 동안 데이터가 웹 페이지에 표시되지 않기 때문에 사용자는 10초간 텅 빈 웹 페이지를 보게 될 것이다.

1) promise를 사용하는 경우

function fetchUser() {
    return new Promise((resolve, reject) => {
    	return userName;
    });
}

위의 코드는 언제 함수를 호출할지는 모르겠지만, 언제든 then이라는 콜백 함수만 등록 해놓으면 user의 데이터가 준비되는 대로 등록한 콜백 함수를 호출하겠다는 의미이다.

하지만 여기서, resolve와 reject를 호출하지 않고 바로 값을 리턴 시키면, Promise {<pending>} 상태가 나오게 된다. 즉, 아무런 호출을 하지 않았기 때문에 '대기' 상태가 나오는 것이다.

Promise에는 Pending(대기), Fulfilled(이행), Rejected(실패) 라는 세가지 상태가 존재하는데, 각각의 의미는 아래와 같다.

  • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
  • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해 준 상태
  • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태
function fetchUser() {
    return new Promise((resolve, reject) => {
    	resolve(userName);
    });
}

그래서 resolve를 사용해 값을 리턴하게 되면, promise의 상태가 Promise {<fulfilled>: "chaticker"}로 바뀌게 되면서 값을 리턴하게 된다.

그 후 then을 사용해 다음 콜백 함수를 실행할 수 있게 된다.

function fetchUser() {
    return new Promise((resolve, reject) => {
    	resolve(userName);
    });
}

const user = fetchUser(); // fetchUser를 통해 값을 받아 오기
user.then(console.log(...))

이 promise를 조금더 간편하게 사용하는 방법인 async & await를 알아보자.

2) async - await를 사용하는 경우

기존 코드에서 함수 앞에 async를 붙여 주게 되면, promise 문법을 사용할 필요 없이 자동적으로 코드 블럭이 promise로 변환된다.

async function fetchUser() {
    return userName;
}

const user = fetchUser(); // fetchUser를 통해 값을 받아 오기
user.then(console.log(...))

그럼, await은 어떤 경우에 사용할까?

function delay(ms){
  	/* 정해진 ms가 지나면 resolve를 리턴하는 promise */
	return new Promise(resolve => setTimeout(resolve, ms));
}

async function getApple(){
	await delay(3000);
  	return '🍎'
}

async function getBanana(){
	await delay(3000);
  	return '🍌'
}

위 코드에서 await을 사용하면 delay 함수를 호출하고, 값을 받아올 때까지 기다리게 된다. 즉, 3초 후에 🍎를 리턴하게 되는 것이다.

위 코드를 promise로 만들어보면,

function getBanana(){
	return delay(3000)
  	.then(() => '🍌');
}

과 같은 코드일 텐데, 이런 식으로 .then()의 체이닝 방식보다는 동기적인 것처럼 보이게 만드는 await을 사용하는 것이 코드 작성이 더 간결하고 이해하기 쉬워진다.

아래 코드는 async & await를 사용하는 명확한 이유에 대한 설명이 될 수 있다.

promise를 사용하면 callback과 마찬가지로 .then() 지옥에 빠질 수 있지만, async & await를 사용하면 콜백 지옥 없이 간결한 코드를 만들 수 있기 때문이다.

/* 기존 promise를 사용하는 경우 */
function pickFruits(){
	return getApple()
  	.then(apple => {
    	return getBanana()
      	.then(banana => `${apple} + ${banana}`);
    });
}
pickFruits().then(console.log);// 6초 뒤 콘솔에 결과 값이 나타남 
/* async & await를 사용하는 경우 */
async function pickFruits(){
  	const apple = await getApple();
  	const banana = await getBanana();
  	return `${apple} + ${banana}`;
}

하지만, 병렬 처리의 경우 각 메서드에 대해 promise를 생성한 후 호출하면 아래와 같이 병렬 처리가 가능하지만, 코드가 길어지고 비효율적이기 때문에 promise의 특정 api인 all() 을 사용하는 것이 효율적이다.

/* async & await으로 병렬처리 할 경우 */
async function pickFruits(){
  	const applePromise = getApple();
  	const bananaPromise = getBanana();
  	const apple = await applePromise();
  	const banana = await bananaPromise();
  	return `${apple} + ${banana}`;
}
/* Promise.all로 병렬처리 할 경우 */
function pickAllFruit() {
	return Promise.all([getApple(), getBanana()]) // 각각의 promise 호출
  	.then(fruits => fruits.join(' + '));
}
pickAllFruit().then(console.log); // 병렬처리가 되어 결과 값이 동시에 출력 됨

참고

profile
개발계발

0개의 댓글