Promise / async await

jj·4일 전

Promise

1. 비동기 처리를 위한 콜백 함수 패턴의 단점

2. 프로미스의 생성

ES6에서부터 이러한 문제점을 해결하기 위해 프로미스가 만들어졌다.

// 프로미스 생성
const promise = new Promise((resolve, reject) => {
  // Promise 함수의 콜백 함수 내부에서 비동기 처리를 수행한다.
  if (/* 비동기 처리 성공 */) {
    resolve('result');
  } else { /* 비동기 처리 실패 */
    reject('failure reason');
  }
});

프로미스 객체는 new Promise()를 이용하여 생성할 수 있다. 프로미스는 콜백 함수를 인자로 전달받는데 이 콜백 함수가 받는 인자는 resolve와 reject이다.
resolve(value): 작업이 성공했을 때 값을 전달한다.
reject(error): 작업이 실패했을 때 에러 값을 전달한다.

프로미스는 다음과 같은 상태 정보를 갖는다.

상태의미상태 변경 조건
pending비동기 처리가 아직 끝나지 않아 결과가 정해지지 않은 상태new Promise()로 생성된 직후의 기본 상태
fulfilled(settled)비동기 처리가 성공적으로 수행됨resolve() 호출
rejected(settled)비동기 처리가 실패함reject() 호출

3. 프로미스의 후속 처리 메서드

프로미스는 비동기 작업이 완료된 후 여러가지 처리를 할 수 있도록 메서드를 제공한다.

  1. then: 비동기 처리가 성공이나 실패했을 때 실행할 콜백 함수를 등록하는 메서드이다.
// fulfilled
new Promise(resolve => resolve('fulfilled'))
  .then(v => console.log(v), e => console.error(e)); // fulfilled

// rejected
new Promise((_, reject) => reject(new Error('rejected')))
  .then(v => console.log(v), e => console.error(e)); // Error: rejected
  1. catch: 프로미스가 rejected 상태로 실패했을 때 호출되는 메서드이다.
// rejected
new Promise((_, reject) => reject(new Error('rejected')))
  .catch(e => console.log(e)); // Error: rejected
  1. finally: 프로미스의 성공, 실패와 무관하게 무조건 한 번 호출되는 메서드이다.
new Promise(() => {})
  .finally(() => console.log('finally')); // finally

4. 프로미스의 에러 처리

then, catch를 사용해 에러 처리를 할 수 있지만 가독성을 위해 catch로 에러 처리하는 것을 권장한다. then과 달리 catch는 앞에서 발생한 모든 then의 에러를 한 번에 잡을 수 있다.

promiseGet('https://jsonplaceholder.typicode.com/todos/1')
  .then(res => console.xxx(res))
  .catch(err => console.error(err)); // TypeError: console.xxx is not a function

5. 프로미스 체이닝

프로미스는 다음 코드와 같이 언제나 프로미스를 반환하므로 연속적으로 호출할 수 있다.이를 프로미스 체이닝이라고 한다.

const url = 'https://jsonplaceholder.typicode.com';

// id가 1인 post의 userId를 취득
promiseGet(`${url}/posts/1`)
  // 취득한 post의 userId로 user 정보를 취득
  .then(({ userId }) => promiseGet(`${url}/users/${userId}`))
  .then(userInfo => console.log(userInfo))
  .catch(err => console.error(err));

6. 프로미스의 정적 메서드

프로미스는 5가지 정적 메서드를 제공한다.

  1. Promise.resolve / Promise.reject: 각자 fulfilled, rejected 상태의 프로미스를 반환한다.
// 배열을 resolve하는 프로미스를 생성
const resolvedPromise = Promise.resolve([1, 2, 3]);
resolvedPromise.then(console.log); // [1, 2, 3]

// 에러 객체를 reject하는 프로미스를 생성
const rejectedPromise = Promise.reject(new Error('Error!'));
rejectedPromise.catch(console.log); // Error: Error!
  1. Promise.all: 여러 개의 비동기 처리를 병렬 처리한다. 하나라도 실패하면 reject 상태가 된다.
const requestData1 = () => new Promise(resolve => setTimeout(() => resolve(1), 3000));
const requestData2 = () => new Promise(resolve => setTimeout(() => resolve(2), 2000));
const requestData3 = () => new Promise(resolve => setTimeout(() => resolve(3), 1000));

Promise.all([requestData1(), requestData2(), requestData3()])
  .then(console.log) // [ 1, 2, 3 ] ⇒ 약 3초 소요
  .catch(console.error);
  1. Promise.race: 가장 먼저 처리 완료(settled)된 것을 따라간다. 가장 먼저 끝난 게 성공이면 성공, 실패면 실패이다.
Promise.race([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
])
  .then(console.log) // 3
  .catch(console.log);
  1. Promise.allSettled: 모든 프로미스가 완료되면 처리 결과를 반환한다. 처리 성공, 실패에 무관하게 모든 프로미스들의 처리 결과를 반환해 준다.
Promise.allSettled([
  new Promise(resolve => setTimeout(() => resolve(1), 2000)),
  new Promise((_, reject) => setTimeout(() => reject(new Error('Error!')), 1000))
]).then(console.log);
/*
[
  {status: "fulfilled", value: 1},
  {status: "rejected", reason: Error: Error! at <anonymous>:3:54}
]
*/

7. 마이크로 태스크 큐

async await

1. async / await

예전에는 제너레이터를 이용해 비동기 처리를 했지만 ES8이후 제너레이터보다 간단하고 가독성이 좋은 async / await 가 도입되었다. 프로미스 기반으로, 별개의 후속 처리가 필요없이 동기 처리처럼 프로미스를 사용할 수 있다.

// async 함수 선언문
async function foo(n) { return n; }
foo(1).then(v => console.log(v)); // 1

// async 함수 표현식
const bar = async function (n) { return n; };
bar(2).then(v => console.log(v)); // 2

// async 화살표 함수
const baz = async n => n;
baz(3).then(v => console.log(v)); // 3

// async 메서드
const obj = {
  async foo(n) { return n; }
};
obj.foo(4).then(v => console.log(v)); // 4

// async 클래스 메서드
class MyClass {
  async bar(n) { return n; }
}
const myClass = new MyClass();
myClass.bar(5).then(v => console.log(v)); // 5

await는 async 함수 내부에서 사용해야 한다.
다만 클래스의 constructor 메서드는 async 메서드가 될 수 없다. 서로 반환하는 값이 인스턴스와 프로미스로 각자 다르기 때문이다.

2. await 키워드

const fetch = require('node-fetch');

const getGithubUserName = async id => {
  const res = await fetch(`https://api.github.com/users/${id}`); // ①
  const { name } = await res.json(); // ②
  console.log(name); // Ungmo Lee
};

getGithubUserName('ungmo2');

await 키워드는 프로미스가 처리 완료 상태가 될 때까지 대기했다가 처리 결과를 반환한다.
그러나 모든 프로미스에 await키워드를 사용하는 것은 지양해야 한다. 서로 연관이 없는 작업을 순서대로 처리하게 되면 시간 낭비가 발생하기 때문이다.
비동기 작업들이 서로 연관이 없는 경우에는 Promise.all 처럼 병렬적 처리를 하는 것이 좋다.

async function foo() {
  const res = await Promise.all([
    new Promise(resolve => setTimeout(() => resolve(1), 3000)),
    new Promise(resolve => setTimeout(() => resolve(2), 2000)),
    new Promise(resolve => setTimeout(() => resolve(3), 1000))
  ]);

  console.log(res); // [1, 2, 3]
}

foo();

3. 에러 처리

async await에서 에러 처리는 try catch문을 사용할 수 있다.
async 함수 내에서 catch문을 사용해서 에러 처리를 하지 않으면 async 함수는 발생한 에러를 reject하는 프로미스를 반환한다. 따라서 async함수를 호출하고 Promise.prototype.catch 후속 처리 메서드를 사용해 에러를 캐치할 수 있다

// 함수 안에서 try/catch
const fetch = require('node-fetch');

const foo = async () => {
  try {
    const wrongUrl = 'https://wrong.url';

    const response = await fetch(wrongUrl);
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error(err); // TypeError: Failed to fetch
  }
};

foo();

// 호출하는 쪽에서 .catch
const fetch = require('node-fetch');

const foo = async () => {
  const wrongUrl = 'https://wrong.url';

  const response = await fetch(wrongUrl);
  const data = await response.json();
  return data;
};

foo()
  .then(console.log)
  .catch(console.error); // TypeError: Failed to fetch
코드 1 (함수 안에서 try/catch)코드 2 (호출하는 쪽에서 .catch)
에러를 잡는 곳foo 내부foo를 호출한 쪽
foo()가 돌려주는 프로미스fulfilled (값은 undefined)rejected
호출한 쪽이 실패를 아는가모름앎
profile
frontend

0개의 댓글