[TIL] 211013

📝 오늘 한 것

  1. await / error handling / await 병렬 처리 / Promise.all() / Promise.race()

📖 학습 자료

  1. 드림코딩 유튜브 '자바스크립트 기초 강의' 13편

📚 배운 것

1. async · await

async는 [TIL] 211012 참고

2) await

promise를 기다리기 위해 사용된다
async가 붙은 함수 안에서만 사용이 가능하다

promise가 await에 넘겨지면
await은 Promise가 수행을 완료하기를 기다렸다가
해당 값을 리턴한다

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


/* 1. async와 await 이용해 '동기적인 코드를 사용하는 것처럼' 작성 */
async function getApple () { // (3) 프로미스를 만든다
  await delay(3000); // (1) delay(3000)가 끝날 때까지 기다렸다가
  return '🍎'; // (2) 사과를 리턴하는
}

getApple()
  .then(console.log); // 3초 뒤에 🍎 출력


/* 2. 원래라면 promise chaining을 이용해 '비동기식으로' 작성 */
function getBanana() {
  return delay(3000)
    .then(() => '🍌');
}

getBanana()
  .then(console.log); // 3초 뒤에 🍌 출력

💡 사과와 바나나의 값을 모두 가져오는 함수를 만들어보자.

  • 내가 작성한 답안

    console 창에 출력되지 않음
    getBanana 콜백 함수를 실행한 후 그 promise의 값을 return 하지 않았기 때문

// 오답
function pickFruits () {
  return getApple()
    .then(apple => getBanana(apple))
    .then(banana => console.log(`${apple} + ${banana}`));
}

pickFruits();
  • 강의 답안

    • getBanana(apple)에서 인수 apple 안 써줘도 됨
    • 인수 지운 getBanana() 앞에 return 추가
    • console.log는 pickFruits 함수 호출 시 then() 안에서 사용되도록 위치 바뀜
      → 최종 결과에는 영향 주지 않지만, pickFruits 함수를 좀 더 깔끔하게 작성 가능(하지만 어쨌거나 콜백 지옥)
// 정답
function pickFruits () {
  return getApple()
    .then(apple => {
      return getBanana()
        .then(banana => `${apple} + ${banana}`);
    });
}

pickFruits().then(console.log);

💡 async와 await을 이용해 콜백 지옥 탈출하기

promise가 await에 넘겨지면
await은 Promise가 수행을 완료하기를 기다렸다가
해당 값을 리턴한다

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

pickFruits().then(console.log);

이처럼 async와 await을 이용해 동기적으로 실행되는 코드를 작성하듯이 간편하게 작성할 수 있다.


💡 에러 처리(Error Handling)

  • pickFruits 함수 안에 try...catch 문을 작성해 에러를 catch 할 수 있도록 한다. try...catch 문을 적지 않으면, console 창에 다음과 같이 '에러 발생!'을 잡을 수 없다는 찐 에러가 뜬다.(Uncaught (in promise) 에러 발생!)
async function getApple () {
  await delay(3000);
  throw '에러 발생!';
  return '🍎';
}

async function pickFruits () {
  try {
    const apple = await getApple();
    const banana = await getBanana();
    return `${apple} + ${banana}`;
  } catch(error) {
    console.log(error); // 에러 발생!
  }
}

pickFruits().then(console.log); // undefined

💡 await 병렬 처리

  • 원래 코드
function delay (ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// getApple 함수에서 사과를 받아오는 데 3초가 걸리고
async function getApple () {
  await delay(3000);
  return '🍎';
}

// getBanana() 함수에서 바나나를 받아오는 데도 3초가 걸린다
async function getBanana () {
  await delay(3000); 
  return '🍌';
}

// 그런데
async function pickFruits () {
  const apple = await getApple(); // (1) 사과를 받아올 때도 await을 써서 기다리도록 하고
  const banana = await getBanana(); // (2) 바나나를 받아올 때도 await을 써서 기다리도록 하면
  return `${apple} + ${banana}`; // (3)
}

// (1)이 끝나야만 (2)가 진행되고, (2)가 끝나야만 (3)이 진행되므로
// 원하는 결과를 얻기까지는 총 6초나 걸린다
// 사과와 바나나를 받아오는 코드는 완전히 독립적이므로, 이렇게 하는 것은 시간 면에서 비효율적이다
  • 수정한 코드
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 () {
  // await이 없으므로 getApple(), getBanana()의 '병렬 실행이 가능하다.'
  const applePromise = getApple(); // getApple 함수에 의해 생성된 promise는, applePromise 변수에 할당된다.
  const bananaPromise = getBanana(); // getBanana 함수에 의해 생성된 promise는, bananaPromise 변수에 할당된다.
  const apple = await applePromise;
  const banana = await bananaPromise;
  return `${apple} + ${banana}`;
}

🔥 코드 분석 (이해를 위해 apple 부분만 가져옴)
읽기 순서 (1) → (2) → (3)

const applePromise = getApple();
// (2) 그런데 이미 (1) 이전에 promise가 생성된 순간
// promise 안의 코드 블럭은 바로 실행된 상태이므로

const apple = await applePromise;
// (1) applepromise가 await에 넘겨지면
// await은 applePromise가 수행을 완료하기를 기다렸다가 해당 값을 리턴하고,
// 이 값이 apple 변수에 할당된다.

// (3) 여기서 applePromise 앞에 await을 써줬어도 추가로 기다릴 필요 없이
// 이미 진행 중인 applePromise의 수행이 완료되면
// applePromise의 resolve가 전달하는 🍎가 바로 apple 변수에 할당된다.

💡 그러나 위의 수정된 코드는 시간 면에서는 효율성이 개선되었을지 몰라도, 여전히 비효율적이다. 서로 독립적으로 수행 가능한 코드들은 promise.all()을 이용해 한층 간결하게 작성할 수 있다.

  • Promise.all()

    매개변수로 promise 배열을 전달하면
    모든 promise들이 병렬적으로 다 받아질 때까지 모아준다

function pickAllFruits () {
  return Promise.all([getApple(), getBanana()])
    .then(fruits => fruits.join(' + '));
}

pikAllFruits().then(console.log); // 🍎 + 🍌

💡 어떤 과일이든 상관없이, 먼저 따지는 과일 하나만을 받아오고 싶은 경우

  • Promise.race()

    배열의 요소들(promise들) 중 가장 먼저 값을 리턴하는 promise를 반환한다.

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

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

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

function pickOnlyOne () {
  return Promise.race([getApple(), getBanna()]);
}

pickOnlyOne().then(console.log); // 🍌

💡 과제 : async와 await을 이용해 기존 코드 다시 작성

콜백 지옥 → ② promise 이용해 콜백 지옥 탈출하기 → ③ async와 await을 이용해 코드 개선

  • async와 await을 이용해 동기식으로 수정한 코드
class UserStorage {
  loginUser (id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (id === 'syong' && password === 'happy' ||
            id === 'corder' && password === 'nice') {
          resolve(id);
        } else {
          reject(new Error('not found'));
        }
      }, 2000);
    });
  }

  getRoles (user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === 'syong') {
          resolve({ name: 'syong', role: 'admin' });
        } else {
          reject(new Error('no access'));
        }
      }, 1000);
    });
  }
}

const userStorage = new UserStorage();

const id = prompt('id를 입력해주세요');
const password = prompt('password를 입력해주세요');

async function completeLogin () {
  try {
    const user = await userStorage.loginUser(id, password);
    const nameAndRole = await userStorage.getRoles(user);
    return `${nameAndRole.role} ${nameAndRole.name}님, 환영합니다!`
  } catch(error) {
    console.log(error);
  }
}

completeLogin().then(alert);

✨ 내일 할 것

  1. 자료 구조와 알고리즘 책 읽기

좋은 웹페이지 즐겨찾기