RISK IT

[TIL18_23.1.26.] promise와 async, await (드림코딩 강의) 본문

IT/TIL

[TIL18_23.1.26.] promise와 async, await (드림코딩 강의)

nomoremystery 2023. 1. 26. 12:12
반응형

1. Promise

promise란

  • Javascript에서 제공하는 비동기를 콜백함수 대신에 간편하게 처리할 수 있도록 도와주는 오브젝트
  • 정해진 장시간의 기능을 수행하고 나서 정상적으로 기능이 수행되었다면 성공의 메시지와 함께 처리된 결과값 전달, 기능이 제대로 수행되지 않았다면 에러를 전달.

promise의 포인트 두 가지

  1. State (상태)
    • process가 heavy한 작업을 수행하고 있는 중인지 또는 기능 수행을 다 완료해서 성공했는지, 실패했는지에 대한 상태에 대해서 이해해야 함
    • state: pending (작업 진행 중) -> fulfilled (작업 완료) or rejected (작업 실패)
    • resolve도 reject도 입력되지 않으면 pending 상태에 있게 됨.
  2. producer와 consumer의 차이
    • 정보를 제공하는 producer와 정보를 사용하는 consumer의 다른 두 가지 견해를 이해해야 함

1. Producer

보통 프로미스 안에서 heavy한 일을 함. 네트워크에서 데이터를 받아오거나 또는 파일에서 큰 데이터를 읽어오는 작업은 시간이 걸리기에 동기적으로 실행하게 되면 다음 작업을 진행할 수 없음. 따라서 이러한 작업은 비동기적으로 실행하는 것이 좋다.

프로미스 안에 네트워크 통신 등의 작업을 하는 코드를 작성할 때 프로미스가 만들어지는 그 순간 네트워크 통신 실행
만약 네트워크 통신을 사용자가 요청하는 순간에만 진행해야 한다면, 프로미스를 만드는 순간 콜백함수가 실행되기 때문에 이 점을 유의해야 한다.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ellie');
  }, 2000);
});

프로미스 안에서는 작업을 성공적으로 수행했다면 ,resolve()라는 콜백함수를 통해 데이터를 받아오면 된다.

2. Consumer

then, catch, finally를 통해 값을 받아올 수 있다.

예시

promise.then((value) => {});

// value에는 프로미스의 resolve 값이 들어간다.

.then(): 프로미스가 정상적으로 잘 수행되어서 최종적으로 resolve를 통해 전달한 값을 value라는 parameter에 저장시킨다.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error('no network'));
  }, 2000);
});

Error()는 자바스크립트에서 제공하는 오브젝트 중 하나. 에러가 발생했다는 것을 알려주는 오브젝트
Error()에는 에러가 발생한 정확한 이유를 명시해 줘야 한다.

promise
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.log(error);
  });

에러는 .catch()라는 함수를 이용하여 에러가 발생했을 때 어떻게 처리할 것인지 콜백 함수를 등록해주면 된다.

promise
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    console.log('finally');
  });

.finally()는 성공하든 실패하든 상관 없이 무조건 실행시켜주는 함수

3. Promise Chaining

✍️ 입력

const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
});

fetchNumber
  .then((num) => num * 2)
  .then((num) => num * 3)
  .then((num) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(num - 1), 1000);
    });
  })
  .then((num) => console.log(num));

💻 출력

5

4. Error Handling

✍️ 입력

const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 1000);
  });
const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${hen} => 🥚`), 1000);
  });
const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
  });

getHen()
  .then((hen) => getEgg(hen))
  .then((egg) => cook(egg))
  .then((meal) => console.log(meal));

💻 출력

🐓 => 🥚 => 🍳

✍️ 입력

const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 1000);
  });
const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
  });
const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
  });

getHen()
  .then((hen) => getEgg(hen))
  .then((egg) => cook(egg))
  .then((meal) => console.log(meal));

💻 출력

Error: error! 🐓 => 🥚
    at promise.js:42:29

에러를 처리하는 .catch()를 사용하지 않아 에러 발생

참고

콜백함수를 전달할 때 받아오는 값으로 다른 함수를 바로 호출하는 경우에는 받아오는 값 생략 가능.

getHen() //
  .then(getEgg)
  .then(cook)
  .then(console.log);

✍️ 입력

const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 1000);
  });
const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
  });
const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
  });

getHen() //
  .then(getEgg)
  .catch((error) => {
    return '🥯';
  })
  .then(cook)
  .then(console.log)
  .catch(console.log);

💻 출력

🥯 => 🍳

에러를 핸들링하기 위해서 .catch()를 이용하여 에러가 발생했을 때도 promise chain이 실패하지 않고 실행됨


저번에 작성했던 callback 지옥 함수를 promise를 사용하여 간단하게 작성

class UserStorage {
  loginUser(id, password, onSuccess, onError) {
    setTimeout(() => {
      if (
        (id === 'ellie' && password === 'dream') ||
        (id === 'coder' && password === 'academy')
      ) {
        onSuccess(id);
      } else {
        onError(new Error('not found'));
      }
    }, 2000);
  }

  getRoles(user, onSuccess, onError) {
    setTimeout(() => {
      if (user === 'ellie') {
        onSuccess({ name: 'ellie', role: 'admin' });
      } else {
        onError(new Error('no access'));
      }
    }, 1000);
  }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage.loginUser(
  id,
  password,
  (user) => {
    userStorage.getRoles(
      user,
      (userWithRole) => {
        alert(
          `Hello ${userWithRole.name}, you have a ${userWithRole.role} role`
        );
      },
      (error) => {
        console.log(error);
      }
    );
  },
  (error) => {
    console.log(error);
  }
);
class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === 'ellie' && password === 'dream') ||
          (id === 'coder' && password === 'academy')
        ) {
          resolve(id);
        } else {
          reject(new Error('not found'));
        }
      }, 2000);
    });
  }

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

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) => alert(`Hello ${user.name}, you have a ${user.role} role`))
  .catch(console.log);

2. async, await

async, await이란?

  • async, awaitpromise가 조금 더 간결하고 동기적인 것처럼 보이도록 만들어준다.
  • promise chaining을 통해 .then()``.then()을 계속해서 추가할 수 있지만 코드가 조금 난잡해질 수 있다. async, await은 이를 조금 더 간편하게 동기식으로 작성할 수 있도록 도와준다.
  • 완전히 새로운 것이 아니라, 기존에 존재하는 promise 위에서 혹은 기존에 존재하는 promise를 감싸서 조금 더 간편하게 사용할 수 있도록 하는 syntactic sugar의 역할을 한다. (또 다른 syntactic sugar로는 class가 있음)
  • 무조건 async, await없는 promise가 나쁜 것은 아님!

기존 Promise 적용 코드 예시

✍️ 입력

function fetchUser() {
  return new Promise((resolve, reject) => {
    resolve('ellie');
  });
}

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

💻 출력

Promise {<fulfilled>: 'ellie'}

같은 코드를 async 활용한 예시

✍️ 입력

function fetchUser() {
  return new Promise((resolve, reject) => {
    resolve('ellie');
  });
}

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

💻 출력

Promise {<fulfilled>: 'ellie'}
ellie

✍️ 입력

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

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

💻 출력

Promise {<fulfilled>: 'ellie'}
ellie

await promise 예시

✍️ 입력

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

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

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

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

pickFruits().then(console.log);

💻 출력

🍎 + 🍌

프로미스도 너무 중첩해서 사용하게 되면 콜백 지옥과 비슷해짐

await과 async를 활용하여 더 간단하게 표현

✍️ 입력

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);

💻 출력

🍎 + 🍌

위 코드는 조금 더 간단해졌지만, pickFruist()에서 getApple()를 호출할 때 1초가 걸리고 그 후 getBanana()를 호출할 때 또 1초가 걸린다. apple과 banana는 독립적으로 실행할 수 있기 때문에 이런 방식은 비효율적이다.

이 때 프로미스를 만들면 프로미스가 바로 실행된다는 점을 이용해서 다음 코드와 같이 작성하면 더 효율적으로 코드를 실행할 수 있다.
✍️ 입력

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 applePromise = getApple();
  const bananaPromise = getBanana();
  const apple = await applePromise;
  const banana = await bananaPromise;
  return `${apple} + ${banana}`;
}

pickFruits().then(console.log);

💻 출력

🍎 + 🍌

하지만 병렬적으로 코드를 수행하는 경우에는 위에 있는 코드보다 더 깔끔하게 작성할 수 있다.
✍️ 입력

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

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

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

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

💻 출력

🍎 + 🍌

Promise.all()이라는 API는 프로미스 배열을 전달하게 되면 모든 프로미스들이 병렬적으로 받아질 때까지 모아주는 역할을 한다.
.then() 이후에 모아진 프로미스들을 .join(' + ')로 묶어줬다.

만약 먼저 따지는(출력되는) 과일만 출력하고 싶다면 다음과 같은 코드를 작성하면 된다.

✍️ 입력

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(), getBanana()]);
}

pickOnlyOne().then(console.log);

💻 출력

🍌

.race() API는 배열에 전달되는 프로미스 중 가장 먼저 값을 리턴하는 프로미스만 전달시킨다.

반응형