[JavaScript] async / await

Soozynn·2022년 7월 6일
0

JavaScript

목록 보기
5/6

이전 편에 이어서 작성해보는 비동기의 HighLight..
비동기의 꽃 🌸 async / await 🌸에 대해 정리해보고자 한다.

Promise를 조금 더 간결하고 간편하게 작성해주기 위한 문법..!
아래 사진처럼 "동기적"으로 실행되는 것처럼 보이게 만드는 문법이다.

출처) 드림코딩

사진처럼 Promise.then을 사용하여 체이닝을 계속해서 하게 되면, 조금 코드가 난잡해질 수 있는데 이런 거 위에 조금 더 간편한 async/await을 사용하게 되면 우리가 그냥 동기식으로 코드를 순서대로 작성하는 것처럼 간단하고 간편하게 작성할 수 있다!

완전히 새로운 문법을 작성하는 것이 아니라 기존의 Promise 위에 조금 더 "설탕"을 뿌린 것이라고 생각하면 된다.

👉 syntatic sugar: 이렇게 기존에 존재하는 거 위에 또는 기존에 존재하는 것을 감싸서 우리가 조금 더 간편하게 쓸 수 있는 API를 제공하는 것.

ex) class 문법 -> 기존에 사용되어진 prototype을 베이스로 한 그 위에 살짝 덧붙여진 개념처럼.

// async / await
// clear style of using promise :) 깔끔하게 프로미스를 사용할 수 있는 방법

// ❗️ 그렇다고 무조건 프로미스가 나쁘고, async / await으로 대체하여 사용해야한다는 것이 아님. 프로미스를 써야 맞는 경우가 있고, async / await을 사용하여야 더 깔끔해지는 경우가 있는데 이는 프로젝트를 통해 감을 익혀가야함.

// 🚀 1. async 써보기
function fetchUser() {
	// do network reqeust in 10 secs..
    // 사용자의 데이터를 백엔드에서 받아오는 함수 & 네트워크 통신을 하여 백엔드에서 데이터를 받아오는데 10초가 걸리는 예제라고 생각해보자.
 
    return "ellie";
}

const user = fetchUser();
console.log(user);
// 

⭐️ 이렇게 무언가 오래 걸리는 코드를 비동기적인 처리를 전혀하지 않으면 자바스크립트 엔진은 동기적으로 코드를 수행하기 때문에 즉, 한 줄이 끝나야 다음 코드가 넘어가는 동기적인 처리를 하기 때문에 10초가 끝날때까지 위 코드에서 머무르다가 해당 시간이 지나고 네트워크 데이터를 성공적으로 받아오게 되면 다음 코드를 읽게 된다.

비동기적인 처리를 전혀하지 않으면 사용자 데이터를 받아오는데 10초가 걸리기 때문에 만약 이후 코드에서 웹 페이지에 UI를 표시하는 그런 기능을 수행하는 코드들이 있다면 이것이 끝나는 동안 데이터가 웹페이지에 표시되지 않기 때문에 사용자는 10초동안 텅텅 빈 웹페이지만 보게 되는 셈이다.

👉 때문에 이렇게 오래걸리는 작업들은 비동기적으로 처리할 수 있게 해주어야한다!

지난 시간은 아래처럼 Promise를 사용하여 만들어주었는데

function fetchUser() {
	// 내가 언제 유저의 데이터를 받아올지는 모르겠지만, 내가 약속할게..
	// ⭐️ 프로미스라는 오브젝트를 가지고 있으면 여기에 너가 then이라는 callback함수만 등록해놓으면 유저에 데이터가 준비되는대로 니가 등록한 callback함수를 불러줄게! 하는 것처럼 약속의 개념.
    
	return new Promise((resolve, reject) => {
    	// do network reqeust in 10 secs..
        // 때문에 이 코드블럭 안에 있는 코드들이 비동기적으로 수행되는 원리.
        return "ellie";
    });
}

const user = fetchUser();
console.log(user);

위 예시에서 Promise 내부에 resolvereject를 사용하지 않고 return하게 되면 콘솔에서 확인해볼 시 pending 단계인 것을 확인할 수 있다.

이 전에 작성한 포스트에서도 적어놨지만, 프로미스는 아래와 같은 3단계로 나뉘어진다.

  • pending: 대기
  • fulfilled: 완료
  • rejected: 실패

❗️ 때문에, 꼭 프로미스 안에는 resolvereject를 사용하여 완료를 해주어야함..!

function fetchUser() {
	return new Promise((resolve, reject) => {
    	// do network reqeust in 10 secs..
        resolve("ellie"); // 콘솔을 통해 확인해보면 fulfilled상태로 바뀐 것을 확인할 수 있다.
    });
}

const user = fetchUser();

// 때문에 user는 결국 프로미스를 리턴하기 때문에 then, catch와 같은 메서드 사용가능!!

user.then(console.log);
console.log(user);

자 이제 위처럼 작성해주지않아도 더 간편하게 비동기를 작성해줄 수 있는 방법이 있다.


바로 함수 앞에 async 라는 키워드를 붙여주는 것이다.

async function fetchUser() {
	return "ellie";
}

const user = fetchUser();
user.then(console.log);
console.log(user);

이전처럼 번거롭게 내부 코드 블럭에 프로미스를 쓰지 않아도 자동적으로 함수 안에 있는 코드 블럭들이 프로미스로 변환되어 지는 것이다!!

위 코드를 실행하게 되면 fetchUserPromise를 리턴하는 것을 볼 수 있다 👀

// 🚀 2. 더 유용한 await 기다려!

function delay(ms) {
	return new Promise(resolve => setTimeout(resolve, ms));
}

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

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

await이란 키워드는 async가 붙은 함수 안에서만 쓸 수 있다.
여기서 위처럼 await을 쓰게되면 비동기작업이 끝날때까지 기다려준다. 따라서 위 코드를 해석해보면 getApple,getBanana 함수 내에서 사용되어지는 각각의 delay가 끝날 때까지 기다리는 것이다.

즉, 3초가 있다가 사과를 리턴하는 프로미스가 만들어지게 되고, 또 3초가 있다가 바나나를 리턴하는 프로미스가 만들어지는 것.

위 예제를 프로미스를 이용하여 다시 작성해보면,

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

이렇게 체이닝을 하는 것보다 async 예제처럼 "동기적인 코드를 쓰는 것처럼" 만들게 되면 더 쉽게 이해를 할 수 있을 것이다.

자 그럼 아래 예시를 다시보자.

// 🚀 2. 더 유용한 await 기다려!

function delay(ms) {
	return new Promise(resolve => setTimeout(resolve, ms));
}

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

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

// ⛔️ 아래와 같이 프로미스 체이닝을 이용하게 되면 like callback hell..
function pickFruits() {
	return getApple.then(apple => {
    	return getBanana().then(banana => `${apple} + ${banana}`);
    });
}

// 위 함수를 최종적으로 부를 때에는 아래처럼 작성하여 다 받아와지면 콘솔이 출력되는 예제
// 6초를 기다린 후 결과 값이 출력이 될 것이다.
pickFruits().then(console.log);

pickFruits함수를 보면 콜백지옥을 떠올리게 된다..
프로미스 또한 너무너무 중첩적으로 체이닝을 하게되면 마찬가지로 콜백지옥과 비슷한 문제점에 빠지게 되는 것이다. 그래서 위를 아래와 같이 async / await을 사용하여 간단하게 만들어 줄 수 있다.

function delay(ms) {
	return new Promise(resolve => setTimeout(resolve, ms));
}

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

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

async function pickFruits() {
	const apple = await getApple();
	const banana = await getBanana();
	return `${apple) + ${banana}`;
}

pickFruits().then(console.log);

프로미스를 사용하였던 위 예제와 비교해보자. 무엇이 다른가?
너무 간단해 진 것을 알 수 있고, 가독성 또한 더 좋아진 느낌..?!

그렇다면, 에러핸들링은 또 어떻게 해줄 수 있을까??
바로 아래처럼 try/catch를 이용하여 핸들링을 해줄 수 있다!

function delay(ms) {
	return new Promise(resolve => setTimeout(resolve, ms));
}

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

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

async function pickFruits() {
	try {
		const apple = await getApple();
		const banana = await getBanana();
	} catch(error) {
   		console.error(error);
	}
    
	return `${apple) + ${banana}`;
}

pickFruits().then(console.log);

들여쓰기 개똥같은 점 양해바랍니다.. ㅠㅠ

그런데, 이렇게 await을 통해 기다려! 하고 기다린 뒤에 또 코드를 실행하고 await을 사용하여 기다리게 되면 코드가 조금 비효율적이게 된다 🤔

바나나와 애플을 받아오는 데는 서로 연관이 없기에 서로 기다릴 필요가 없지 않을까?
때문에 위 코드를 더 개선해보면,

// 🚀 3. await 병렬 처리

function delay(ms) {
   return new Promise(resolve => setTimeout(resolve, ms));
}

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

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

async function pickFruits() {
	// 1. apple promise 만들기
    // ❗️ 아래처럼 바로 만들면, 프로미스를 만드는 순간 바로 프로미스 안에 들어있는 코드 블럭이 실행되어짐.
    const applePromise = getApple();
   	const bananaPromise = getBanana();
    
    // 이제 아래처럼 여기에서 동기화를 시켜줌.
    const apple = await applePromise;
    const banana = await bananaPromise;
    
	return `${apple) + ${banana}`;
}

pickFruits().then(console.log);

위처럼 실행하게 되면, 더 빠르게 코드가 실행되어지는 것을 확인할 수 있다.
즉, 사과와 바나나를 동시에 따서 한 번에 기다렸다가 출력하게 되는 것이다.

⭐️ 서로 연관이 없어 병렬적으로 기능을 수행할 수 있는 경우에는 위처럼 더럽게 코드를 작성하지 않고
유용한 Promise API -> Promise.all을 사용하면 된다.

// 🚀 4. useful Promise APIs -> Promise.all 사용하기

function delay(ms) {
	return new Promise(resolve => setTimeout(resolve, ms));
}

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

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

function pickAllFruits() {
    // 이것은 프로미스 배열을 전달하게 되면 모든 프로미스들이 병렬적으로 다 받을 때까지 모아주는 아이.
	return Promise.all([getApple(), getBanana()]).then(fruits => 
    	fruits.join(' + ')
    );
}

pickAllFruits().then(console.log);


// or
// 먼저 따지는 첫 번째 과일만 받아오고 싶다면 -> Promise.race APIs
// 사과를 따는데 2초 바나나 따는데 1초로 가정
function pickOnlyOne() {
	return Promise.race([getApple(), getBanan()]);
}

pickOnlyOne().then(console.log);
// 콘솔에 바나나가 출력되는 것을 볼 수 있다.

Promise 에서 thencatch, finally API가 있는 것은 알았으나 이 외에 allrace가 있는 것은 이번 강의를 통해 처음 알게 되었다. 호옹..🤔


이 외에 HOMEWORK..!

지난 시간에 callback을 Promise로 만들어주었던 부분을 다시 async/await으로 깔끔하게 만들어보기

// 🚀 이전에 작성한 promise 코드

const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter yout password");

userStorage.loginUser(id, password)
	.then(userStorage.getRoles)
    .then(user => alert("어쩌구 저쩌구 전하고자 하는 경고 내용.."));
    .catch(console.error);

위와 아래 코드 비교해보기!

// 🚀 async / await 로 변경한 코드

const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter your password");

async function checkUser() {
  try {
    const userId = await userStorage.loginUser(id, password);
    const userInfo = await userStorage.getRoles(userId);
    alert(`Hello ${userInfo.name}, you have a ${userInfo.role}`);
  } catch (error) {
    console.error(error);
  }
}

checkUser();

0개의 댓글