TIL 24. JS Promise와 async & await

자바스크립트의 Promise와 async & await개념에 대해 알아보겠습니다. 이 글은 MDN와 캡틴판교을 토대로 작성되었습니다.

Promise

“A promise is an object that may produce a single value some time in the future”

프로미스는 자바스크립트 비동기 처리에 사용되는 객체이다. 프로미스는 우리가 아직 모르는 미래의 값을 다룰 수 있도록 해준다.

Promise를 씀으로써 우리는 callback함수로 어떤 결과가 충족되었는지 거절되었는지에 따라서 다르게 처리할 수 있게 된다. 본질적으로, Promise는 보통의 함수에 callback을 인자값으로 넣는 대신에 객체를 return 받는 것이다.

비동기적인 함수로부터 동기적으로 결과를 return 받을 수 있다.

Guarantees

then메서드 안에 들어간 callback함수는 자바스크립트 이벤트 루프 상에서 해당 처리가 끝나기 전까지 절대 호출되지 않는다.

callback 함수들은 Promise가 나타내는 비동기적인 실행이 성공 혹은 실패한 이후에 처리되더라도 호출된다.

Promise의 3개 스텝

  1. Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
  2. Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
  3. Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

예시 : 엄마의 랜덤 기분에 따라 핸드폰을 받거나, 받을 수 없는 Promise

const isMomHappy = Math.random();

// Promise
const willIGetNewPhone = new Promise(function (resolve, reject) {
  if (isMomHappy > 0.3) {
    const phone = {
      brand: "Samsung",
      color: "black",
    };
    // 3초 setTimeout
    setTimeout(() => resolve(phone), 3000); // if fulfilled
  } else {
    const reason = new Error("mom is not happy");
    reject(reason); // if rejected
  }
  
  // 동기적인 코드
  console.log(willIGetNewPhone) // Promise{<pending>}
  // setTimeout이지만 3초가 되기 이전이므로 pending.
  setTimeout(() => console.log(willIGetNewPhone), 1000); // Promise{<pending>}
  setTimeout(() => console.log(willIGetNewPhone), 2000); // Promise{<pending>}
  
  // 4초 후 참조하는 Promise : 여기선 슬프게도 rejected라는 결과가 나왔다.
  setTimeout(() => console.log(willIGetNewPhone), 4000);
// Promise {<rejected> Error: mom is not happy}

그런데 위의 코드는 setTimeout으로 정한 시간을 매번 확인해야 한다는 단점이 있다. 그리고 setTimeout을 사용했기 때문에 시간이 얼마나 걸릴지 예측할 수 있지만, 서버에서 데이터를 받을 때엔 어떻게 할까? 데이터를 받은 직후에 callback함수를 실행할 방법은 없을까?

then 메서드

// then 메서드를 이용해서 pending이 끝난 후에 주어진 callback함수를 실행한다.
willIGetNewPhone.then((value) => console.log(value));

Promise의 에러 처리 방법

// UnhandledPromiseRejectionWarning: Error: mom is not happy

위의 코드에서는 rejected의 경우일 때, Error 처리를 하지 않아 나머지 모든 코드가 멈추게 된다.
Promise에서 에러를 처리하는 대표적인 방법은 .catch이다. 이를 통해서 우리는 코드가 멈추게 하는 대신에 안정적으로 에러를 처리할 수 있게 된다.

willIGetNewPhone
  .then((value) => console.log(value))
  .catch((error) => console.log(error));

onfulfilled

onfulfilled라는 숨겨진 property가 있다. 이건 서버쪽에서 요청이 승인되고 value를 받았다는 data. 자바스크립트 엔진 입장에선 이 property가 채워지기까지 web API를 통해 기다린다고 보면 된다.

그리고 onfulfilled가 충족되었을 때, then()에 callback으로 보낸 함수가 execute된다.

onfulfilled는 숨겨진 property이다. 그래서 접근은 할 수 있지만 바꿀 수는 없다.

결과는 callback만이 안다.

Only the function responsible for creating the promise will have knowledge of the promise status, or access to resolve or reject.

Error 객체

When I reject() with a value, I always pass an Error object. Generally I want two possible resolution states: the normal happy path, or an exception — anything that stops the normal happy path from happening. Passing an Error object makes that explicit.

async & await

The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.

async와 await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다. 기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와줌.

async & await 사용법

async function 함수명() {
  await 비동기_처리_메서드_명();
}

먼저 함수의 앞에 async 라는 예약어를 붙인다. async키워드는 Promise객체를 생성하도록 만든다. 그러고 나서 함수의 내부 로직 중 HTTP 통신을 하는 비동기 처리 코드 앞에 await를 붙인다. 여기서 주의해야 할 점은 비동기 처리 메서드가 꼭 프로미스 객체를 반환해야 await가 의도한 대로 동작한다.

일반적으로 await의 대상이 되는 비동기 처리 코드는 Axios 등 프로미스를 반환하는 API 호출 함수입니다.

에러 처리 : try & catch

프로미스에서 에러 처리를 위해 .catch()를 사용했던 것처럼 async에서는 catch {} 를 사용하시면 됩니다.

네트워크 통신 오류뿐만 아니라 간단한 타입 오류 등의 일반적인 오류까지도 catch로 잡아낼 수 있습니다. 발견된 에러는 error 객체에 담기기 때문에 에러의 유형에 맞게 에러 코드를 처리해주시면 됩니다.

async function logTodoTitle() {
  try {
    var user = await fetchUser();
    if (user.id === 1) {
      var todo = await fetchTodo();
      console.log(todo.title); // delectus aut autem
    }
  } catch (error) {
    console.log(error);
  }
}

좋은 웹페이지 즐겨찾기