1. async와 await란?
- async와 await는 지난 포스팅에서 설명한 promise를 더욱 간결하고 동기적으로 실행되는 것처럼 보이게 만들어 주는 기능이다.
- 지난 포스팅에서 then을 사용하여 promise chaining을 구현했는데, 직관적이고 사용하기 편하지만 코드 자체를 봤을 때에는 복잡해 보인다는 것이 단점이다.
- 이러한 단점을 보완할 수 있도록, promise를 동기적으로 실행하는 것처럼 보이게 하는 역할이 async와 await다.
- 프로그래밍 용어로, async와 await같은 기능들을 sytactic sugar라고 한다.
*syntactic sugar - async와 await처럼, 기존에 존재하는 기능을 더욱 사용하기 편하도록 제공되는 API를 가리킨다.
2. async 사용법
- promise를 사용할 때, 항상 async와 await를 사용해야만 하는 것은 아니다.
- promise의 코드가 복잡해 보일 때, 이를 조금 더 깔끔한 형태로 만들어주기 때문에 상황에 맞게 사용한다.
a) async 코드구현
- async 사용법을 설명하기 앞서, promise가 어떤식으로 처리되는지 예시를 만들어 보자.
- 네트워크 request를 통해 사용자 정보를 받아오는데 10초가 걸리는 메소드가 있다고 가정하자.
function fetchUser() {
// 여기서 10초 후에 반환값이 return 된다고 가정해보자.
return "dev_raphy";
}
const user = fetchUser();
console.log(user);
- JavaScript는 동기적인 처리를 하는 언어이기 때문에, fetchUser()처럼 처리가 오래 걸리는 코드를 비동기적 처리를 하지 않으면 다음 코드로 넘어가지 못하여 해당 코드(fetchUser)의 처리가 끝날 때 까지 머무르게 된다. 즉, 사용자가 10초를 기다려야되는 상황이 발생한다.
- 이를 해결하기 위해, 비동기적 처리로 사용했던 방법이, 지난 시간에 포스팅했던 promise다.
function fetchUser() {
return new Promise((resolve, reject) => {
return "dev_raphy";
});
}
const user = fetchUser();
console.log(user);
- 만약 promise를 작성한 후 resolve와 reject를 이용해 완전히 처리하지 않으면 promise는 pending상태가 된다.
- 그러므로 promise를 사용한다면, 반드시 resolve와 reject를 이용하여 성공/실패 케이스의 처리를 완료해야 한다.
- resolve 또는 reject를 이용하여 처리를 한다면, 위의 사진처럼 조건을 만족한다는 fulfilled 상태로 바뀌게 된다.
function fetchUser() {
return new Promise((resolve, reject) => {
resolve("dev_raphy");
});
}
const user = fetchUser();
console.log(user);
// user.then(변수명) => console.log(변수명)
// * 변수명 = 개발자가 직접 설정, fetchUser의 return값을 담는다.
user.then((userName) => console.log(userName));
user.then(console.log); // 위의 코드와 동일한 코드, 매개변수가 동일하다면 생략가능
- promise를 작성하지 않고도 간편하게 비동기식 처리를 하는 방법이 있는데, 이것이 async다.
async function fetchUser() {
return "dev_raphy";
}
const user = fetchUser();
console.log(user);
user.then((userName) => console.log(userName));
user.then(console.log);
- promise를 작성하지 않아도 async를 사용하면 함수안의 코드가 내부적으로 promise 형식으로 사용된다.
- 즉, promise를 작성하지는 않아도 간편하게 promise를 사용할 수 있게 되는 것이다.
3. await 사용법
- await는 async가 붙은 함수 안에서만 사용할 수 있는 기능이다.
- 예시를 보면서 이해해보자.
a) awiat 코드구현
- ms(밀리세컨드)를 매개변수로 하는 delay()라는 함수가 있다.
- 매개변수에 따라 delay되는 시간을 설정할 수 있는 기능의 함수이다.
- 정해진 시간이 끝난 뒤에 resove를 호출하게 된다.
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
- 이 delay()라는 함수를 이용하여 다음과 같은 코드를 작성해보았다.
async function getApple() {
await delay(3000);
return "🍎";
}
async function getBanana() {
await delay(3000);
return "🍌";
}
- 위의 코드에서, getApple()과 getBanana()는 3초가 지난 후에 resolve를 전달하는 함수다.
- 여기서 await를 사용하게 되면, delay()가 끝날 때까지 기다린 후에 사과/바나나를 return하는 promise가 실행되는 것이다.
- await를 사용하지않고 위의 코드를 작성하게 된다면 다음과 같다.
async function getApple() {
return delay(3000).then(() => "🍎");
}
async function getBanana() {
return delay(3000).then(() => "🍌");
}
- await를 사용했을 때와 사용하지 않았을 때의 코드를 비교해보자.
- await를 사용하면 delay()가 실행된 후에 return이 되어 동기적인 처리를 하는 것처럼 보이는 코드를 작성한다.
- 반면에 await를 사용하지 않으면 await를 사용한 코드와 비교했을 때 상대적으로 코드의 가독성이 떨어지고, 복잡한 경우에는 코드를 이해하기 어려울 수 있다.
- 자, 이제 위의 getApple()과 getBanana()함수를 모두 호출하여 출력하는 pickFruits()라는 함수가 있다고 가정해보자.
- await를 사용하지 않으면 다음과 같이 작성할 수 있다.
function pickFruits() {
return getApple().then((apple) => {
getBanana().then((banana) => `${apple} + ${banana}`);
});
}
pickFruits().then(console.log);
- 위의 pickFruits()의 콜백함수들을 보면 이전 포스팅에서 보았던 코드와 비슷한 양상을 보인다. 무엇일까?
- 바로... 콜백 지옥이다. 콜백 안에도 다시 콜백을 함수하는 콜백 지옥코드와 동일한 양상을 보이게 된다.
- 이제, async를 사용해서 위의 코드를 다시 작성해보자.
async function pickFruits() {
const apple = await getApple();
const banana = await getBanana();
return `${apple} + ${banana}`;
}
- async와 await를 사용하면 콜백지옥과 같은 양상을 보였던 코드와는 다르게 깔끔한 코드를 작성할 수 있는 것을 볼 수 있다.
- 그러나, 위의 코드에서도 개선해야할 점이 한가지 있다. getApple()과 getBanana()를 실행하는데 각각 3초, 총 6초의 시간이 걸리는 부분이다.
- getApple()과 getBanana()는 전혀 연관이 없는 함수이기 때문에, 하나가 완료될 때까지 기다리는 것이 아니라 병렬적으로 처리할 수 있기 때문이다.
c) 에러처리
- 지금까지는 resolve를 구현하는 코드를 작성해보았다. 그렇다면 reject 또는 에러처리는 어떻게 작성할 수 있을까?
- 에러처리 부분은 try ~ catch를 사용하여 구현할 수 있다.
async function pickFruits() {
try {
const apple = await getApple();
const banana = await getBanana();
} catch {
// 에러처리 구문 작성부분
}
return `${apple} + ${banana}`;
}
b) 병렬처리
- promise를 선언하면 hoisting으로 인해, 바로 코드가 읽힌다는 것을 지난 포스팅에서 배웠다. 이 특성을 사용하여 병렬처리를 구현할 수 있다. * 병렬처리 - 다른 연산이 동시에 수행되는 것
async function pickFruits() {
const applePromise = getApple();
const bananaPromise = getBanana();
const apple = await applePromise;
const banana = await bananaPromise;
return `${apple} + ${banana}`;
}
pickFruits().then(console.log);
- 위의 코드처럼 작성하게되면, getApple()과 getBanana()가 동시에 처리되어 6초를 기다리는 것이 아니라 3초만에 출력되는 것을 확인할 수 있다.
d) 개선된 병렬처리 방법
- 위에서 설명한 병렬처리 방법은 실제로 많이 사용되는 병렬처리 방법이 아니다.
- 현업에서는 promise를 이용한 병렬처리를 할 때, promise API에 있는 함수를 이용한다.
- 다음과 같은 방법으로 사용한다.
function pickAllFruits() {
return Promise.all([getApple(), getBanana()]).then((fruits) =>
fruits.join(" + ")
);
}
pickAllFruits().then(console.log);
- Promise.all() 함수는 배열을 이용하여 개발자가 원하는 promise를 매개변수로 전달하여 병렬처리하는 방식이다.
- Prmoise.all() 함수는 반환된 promise를 배열 형식으로 return해주기 때문에 배열에 담겨있는 요소들을 문자열로 변환해주는 join() 함수를 사용하여 출력해줄 수 있다.
e) Promise.race()
- 만약 우선적으로 완료된 promise만 받아오고 싶다면 어떻게 구현할까?
- getApple()이 1초가 걸리고 getBanana()가 3초가 걸린다면, 더 빠르게 처리된 getApple()만 받아서 출력하는 예시를 코드로 구현해보자.
function pickOnlyOne() {
return Promise.race([getApple(), getBanana()]);
}
pickOnlyOne().then(console.log);
- Promise.race() 함수는 Promise.all()과 동일하게 여러개의 promise를 배열형태로 전달하여, 가장 먼저 처리가 끝나는 promise의 반환값만을 받아와서 return하는 함수이다.
- getApple() 함수가 1초만에 처리되고 getBanana()는 3초가 걸리기 때문에, 사과만 반환되어 출력되는 것을 볼 수 있다.
4. 코드개선
- 지난 포스팅에서 콜백지옥이 된 코드를 promise를 사용하여 개선하였다.
- 이번에는 promise로 개선된 코드의 customer부분을 async와 await를 사용하여 더욱 깔끔하게 만들어보자.
[개선해야 할 코드 - 지난 포스팅에 작성된 코드]
"use strict";
// Producer
class UserStorage {
loginUser(id, password) {
// 기존 코드의 성공,실패여부를 관리하던 메소드를 promise로 대체한다.
return new Promise((resolve, reject) => {
setTimeout(() => {
if (
(id === "kevin" && password === "kevin123") ||
(id === "admin" && password === "master")
) {
resolve(id);
} else {
reject(new Error("not found"));
}
}, 2000);
});
}
getRoles(user) {
// 기존 코드의 성공,실패여부를 관리하던 메소드를 promise로 대체한다.
return new Promise((resolve, reject) => {
setTimeout(() => {
if (user === "kevin") {
resolve({ name: "kevin", role: "developer" });
} else {
reject(new Error("not found"));
}
}, 2000);
});
}
}
// Customer
const userStorage = new UserStorage();
const id = prompt("enter your id: ");
const password = prompt("enter your password: ");
userStorage
.loginUser(id, password)
.then(userStorage.getRoles) // .then((user) => userStorage.getRoles(user));
.then((user) => alert(`Hello, ${user.name}. You have a ${user.role} role.`))
.catch(console.log); // .catch((error) => console.log(error));
[async와 await를 사용하여 개선된 코드]
// Customer
async function getUserInfo() {
const userStorage = new UserStorage();
const id = prompt("enter your id: ");
const password = prompt("enter your password: ");
try {
const loginId = await userStorage.loginUser(id, password);
const loginRole = await userStorage.getRoles(loginId);
return alert(
`Hello, ${loginRole.name}. You are a fantastic ${loginRole.role}!!!`
);
} catch {
return alert(`error: ${new Error("not found")}`);
}
}
getUserInfo();
[정보출처]
www.youtube.com/watch?v=aoQSOZfz3vQ&list=PLv2d7VI9OotTVOL4QmPfvJWPJvkmv6h-2&index=13
'Front-end > Vanilla JS' 카테고리의 다른 글
JS 근본 공부 - Promise (2) | 2020.11.06 |
---|---|
JS 근본 공부 - 콜백(Callback) (0) | 2020.11.05 |
JS 근본 공부 - JSON (0) | 2020.11.04 |
JS 근본 공부 - 배열 API 연습문제 (0) | 2020.11.03 |
JS 근본 공부 - 배열(Array) (0) | 2020.11.02 |
댓글