// CASE1
hen -> egg -> fry
// CASE2
Error: no Egg!
at Timeout._onTimeout (D:\dev\yakbang_front\promise.js:45:29)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7)
bread -> fry
예제
위에서 작성했던 콜백지옥을 Promise를 사용하여 레벨업을 시켜보자
class UserStorage {
loginUser(id, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id === "apple" && password === "banana") {
resolve(id);
} else {
reject(new Error("wrong user info"));
}
});
});
}
getRoles(user) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (user === "apple") {
resolve({ id: "apple", role: "admin" });
} else {
reject(new Error("no role"));
}
});
});
}
}
const userStorage = new UserStorage();
const id = "apple";
const password = "banana";
userStorage
.loginUser(id, password)
.then((id) => userStorage.getRoles(id))
.then((user) => console.log(`Welcom ${user.id} Role : ${user.role}`))
.catch((error) => console.log(error));
이제 구현부의 내용을 보면, 순차적으로 실행되는 로직이 눈에 들어온다
async-await
Promise 를 간결하게 표현, 동기적으로 실행되는 것 처럼 표현
Promise도 좋은데, 좀 더 좋은 방법 없을까? -> 갑자기 new Promise 등장하는게 읽기 불편해
syntactic sugar : 코드상 슈가 → 기존에 있는 기술을 좀 더 편하게 사용할 수 있도록 하는 것
모든 Promise를 대체하는 건 아니다
동기적인 실행
apple을 받아오는데 2초가 걸리는데, 다 기다렸다가 Next Content 실행
function sleep(ms) {
const wakeUpTime = Date.now() + ms;
while (Date.now() < wakeUpTime) {}
}
function fetchUser() {
sleep(2000);
return "apple";
}
const user = fetchUser();
console.log("user", user);
console.log("Next Content");
-> 2초 뒤 콘솔로그
Promise 로 비동기적 실행 구현 (뒤에 관련 없는 Next Content를 먼저 표현한다)
function sleep(ms) {
const wakeUpTime = Date.now() + ms;
while (Date.now() < wakeUpTime) {}
}
function fetchUser() {
return new Promise((resolve, reject) => {
sleep(2000);
resolve("apple");
});
}
const user = fetchUser();
user.then(console.log);
console.log("Next Content");
함수 앞에 “async”를 붙여주면 자동으로 Promise로 return 해준다
Producer (Promise를 리턴하는 부분) 에서 Promise를 간결하게 표현하는 방법
function fetchUser() {
return new Promise((resolve, reject) => {
sleep(2000);
resolve("apple");
});
}
// 위와 아래는 똑같은 결과를 준다
async function fetchUser() {
sleep(2000);
return "apple";
}
“await”은 “async”가 붙은 함수 안에서만 사용 가능
“async” (Promise) 안에서 동기적으로(다 할때까지 기다려) 코드가 실행되도록 하는 장치
await 없이 delay 함수 실행하면?
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchUser() {
delay(2000);
return "apple";
}
const user = fetchUser();
user.then(console.log);
console.log("Next Content");
바로 실행된다
why? fetchUser() 함수 안에서 delay를 기다리지 않고, 바로 apple을 return 해줌
awiat delay 주면 ?
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchUser() {
await delay(2000);
return "apple";
}
const user = fetchUser();
user.then(console.log);
console.log("Next Content")
-> 2초 뒤에
2초 동안 기다렸다가 apple을 리턴해줌
Next content가 먼저 표현되고, apple을 2초 뒤에 표현
async-await을 통해서 코드를 동기적으로 표현
가독성이 좋아지고 에러처리도 간편하다 try-catch
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchUser() {
await delay(2000);
console.log("2초 뒤");
return "apple";
}
async function fetchRole() {
await delay(1000);
console.log("1초 뒤");
return "admin";
}
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchUser() {
await delay(2000);
console.log("2초 뒤");
return "apple";
}
async function fetchRole() {
await delay(1000);
console.log("1초 뒤");
return "admin";
}
async function fetchUserAndRole() {
try {
const user = await fetchUser();
const role = await fetchRole();
return `${user} & ${role}`;
} catch (error) {
console.log(error);
}
}
fetchUserAndRole().then(console.log);
console.log("Next Content");
단점) 만약 user & role이 서로 연관 없는 데이터를 가져오는 행동이라면 불필요한 시간 소모가 된다
-> 2초 user 가져오고 난 후에 1초 role을 가져와서 총 3초가 걸린다
-> user 가져옴과 동시에 role을 병렬적으로 가져오면 총 2초로 단축 가능
해결책
Promise는 생성과 동시에 실행되는 성격을 이용하자
async function fetchUserAndRole() {
const userPromise = fetchUser(); // promise 만들자 마자 실행
const rolePromise = fetchRole(); // promise 만들자 마자 실행
const user = await userPromise;
const role = await rolePromise;
return `${user} & ${role}`;
}
Better Expression
모든 promise를 한 줄 씩 각각 쓰지말고 -> Promise.all로 전체 실행 가능\
단, 아예 실행이 안되는것이아니라, 내부적으로 콘솔로그나 로직은 모두 실행이 되는데, 최종 return 값만 가장 빨리 되는 놈으로 가져오는 것임!
예제
위에서 작성했던 콜백지옥 -> Promise를 최종적으로 Async-await을 이용해 작성해보자
class UserStorage {
delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async loginUser(id, password) {
await this.delay(2000);
if (id === "apple" && password === "banana") {
return id;
} else {
throw "wrong user Info";
}
}
async getRoles(user) {
await this.delay(1000);
if (user === "apple") {
return { id: "apple", role: "admin" };
} else {
throw "no role";
}
}
}
const userStorage = new UserStorage();
const id = "apple";
const password = "banana";
async function fetchUserAndRole(id, password) {
const user = await userStorage.loginUser(id, password);
const userWithRole = await userStorage.getRoles(user);
return `Welcome ${userWithRole.id}, Role : ${userWithRole.role}`;
}
fetchUserAndRole(id, password).then(console.log);
가장 가독성이 좋다 따로 try-catch로 에러처리는 하지 않았지만, Class 내부 method에서 예외발생시 throw로 던져주고, 최종 구현부(fetchUserAndRole)에서 try - catch로 감싸면 메소드들에서 던진 예외상황들이 catch로 다 모여서 걸러진다