비동기 처리를 위한 Promise & async & await

  • async와 await는 promise를 조금 더 간결하고 간편하게, 동기적으로 실행되는 것 처럼 보이게 만들어줌
  • 즉, promise 에서 사용하는 then을 무수히 남발할 필요가 없음
  • 새로운 것이 추가된 것이 아닌, 기존에 존재하던 promise위에 조금 더 간편한 API를 제공하는 것
  • Class와 같이 ‘syntactic sugar’로 볼 수 있음

비동기처리를 안했을 경우

//다음과 같이 비동기적인 처리를 전혀 하지 않은 경우, 자바스크립트는 fetchUser()를 실행하면서 10초동안 서버에서 데이터를 받아와서 리턴할때까지 기다린 후에 다음 라인으로 넘어간다.

function fetchUser(){
    // do network request in 10 secs....
    return 'Dylan';
}

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

Promise 를 이용한 비동기 처리

// 언제 데이터를 서버로부터 user의 데이터를 받아올진 모르겠지만, Promise라는 오브젝트를 정의해두고, 여기에 then이라는 콜백함수만 등록해놓으면 유저의 데이터가 준비되는대로 불러주겠다고 약속하는것
function fetchUser() {
  return new Promise((resolve, reject) => {
      //do network in 10 secs
    resolve("dylan");
  });
}

const user = fetchUser();
user.then(console.log); // 10초후 dylan 출력

async를 이용한 비동기처리

// 1. async
// async를 사용하면 번거롭게 Promise를 쓰지않아도 자동적으로 함수안에 있는 코드블록들이 Promise로 변환됨
// syntactic sugar

async function fetchUser() {
  //do network request in 10 secs...
  return 'dylan'; 
}

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

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 "🍌";
}

function pickFruits() {
  return getApple().then((apple) => {
    return getBanana().then((banana) => `${apple} + ${banana}`);
  });
}

pickFruits().then(console.log); // 🍎 + 🍌
  • 위의 코드와 같이 Promise도 중첩적으로 체이닝을 하면 콜백지옥과 비슷한 문제점(가독성 등)이 발생한다..
  • 그래서 await을 이용
function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

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

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

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

에러 핸들링

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

async function getApple() {
  await delay(1000);
  throw 'get apple error';
  return "🍎";
}

async function getBanana() {
  await delay(1000);
  throw 'getbanana error'
  return "🍌";
}

async function pickFruits() {
  try {
    const apple = await getApple();
    const banana = await getBanana();
  } catch(){
    console.log(error);
  }
  return `${apple} + ${banana}`;
}
pickFruits().then(console.log);
  • 위의 코드를 보면 getApple()에서 1초, getBanana() 에서 1초가 소요된다.
    •  저 두 함수 간에는 연관이 없으므로 조금더 효율적으로 만들수 있다.
  • 서로 연관이 없으므로 병렬으로 처리하는 것이 좋다
function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function getApple() {
  await delay(1000);
  throw 'error';
  return "🍎";
}

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

async function pickFruits() {
    const applePromise = getApple(); // 만들자마자 getApple()코드 실행
    const bananaPromise = getBanana(); // 만들자마자 getBanana()코드 실행
    const apple = await applePromise; // await로 기다림 (동기화)
    const banana = await bananaPromise; // 
  
  return `${apple} + ${banana}`; // 1초만에 완료
}
pickFruits().then(console.log);
  • 하지만 위의 방법보다 더 쉽게 만들수 있는 API를 제공한다

useful APIs

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

async function getApple() {
  await delay(1000);
  throw "error";
  return "🍎";
}

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

// async function pickFruits() {
//   const applePromise = getApple(); // 만들자마자 getApple()코드 실행
//   const bananaPromise = getBanana(); // 만들자마자 getBanana()코드 실행
//   const apple = await applePromise; // then 대신 await로 기다림 (동기화)
//   const banana = await bananaPromise;

//   return `${apple} + ${banana}`;
// }

pickFruits().then(console.log);

// 3. useful Promise APIs
// Promise.all([]) : 프로미스 배열을 전달하게 되면 모든 프로미스들이 병렬적으로 다 리턴 받아질때까지 모아줌
function pickAllFruits() {
  return Promise.all([getApple(), getBanana()]).then((fruits) =>
    fruits.join("+")
  );
}
pickAllFruits().then(console.log); // 🍎 + 🍌
function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function getApple() {
  await delay(1000);
  throw "error";
  return "🍎";
}

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

// Promise.race([]);
// 어떤것이든 상관없고 먼저 리턴되는 하나만 받아오고 싶을때
function pickOnlyOne(){
    return Promise.race([getApple(),getBanana()]); // Promise.race는 먼저 수행되는 애만 리턴
}
pickOnlyOne().then(console.log); // 🍌

이전 유저 로그인 예제 변환(promise -> async & await)

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === "dylan" && password === "test") ||
          (id === "coder" && password === "test")
        ) {
          resolve(id);
        } else {
          reject(new Error("not found"));
        }
      }, 2000);
    });
  }
  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === "dylan") {
          resolve({ name: "dylan", role: "admin" });
        } else {
          reject(new Error("no access"));
        }
      }, 1000);
    });
  }
}
let id = prompt('input your id ');
let passwd = prompt('input your password');

const userStorage = new UserStorage();
async function getUserWithRole(id,passwd) {
    const login = await userStorage.loginUser(id, passwd);
    const role = await userStorage.getRoles(id);
    return alert(`Hello ${login}, you have a ${role.role} role`);
}
getUserWithRole(id,passwd)
.then(console.log)
.catch(console.log);