개발

자바스크립트의 비동기 처리 - Promise

이미지 출처: Pixabay

Promise

자바스크립트는 비동기 처리를 위한 하나의 패턴으로 Callback Function을 사용합니다. 그러나 전통적인 Callback 패턴은 코드의 가독성을 해치고 비동기 작업 중 발생한 에러의 예외 처리가 곤란하며 여러 개의 비동기 처리 로직을 한꺼번에 처리하는 것에 대한 한계를 보여줍니다. ES6에서 추가된 Promise는 이런 기존의 Callback 패턴이 가진 단점을 보완하며 비동기 처리 시점을 명확하게 표현합니다.
// 👍 화살표 함수를 사용하여 코드의 가독성과 간결성을 챙길 수 있습니다. 
const asyncWork = new Promise((resolve, reject) => {
  if (// 비동기 작업의 성공 여부) {
    resolve("작업 성공");
  } else {
    resolve("작업 실패");
  }
});

위 함수는 Promise의 기본적인 사용법을 담은 코드입니다. Callback으로 resolvereject를 가지고 있습니다. 이때 Promiseresolvereject를 반환하지 않으면 Pending 상태로 들어가게 되어있습니다.

 

이미지 출처: MDN - Mozilla

fetch('https://jsonplaceholder.typicode.com/users', {
  method: 'GET',
})
  .then((response) => {
    const { status } = response;
    console.log(status);
  })
  .catch((error) => {
    const { message } = error;
    console.error(message);
  });

위 코드는 자바스크립트에서 지원하는 Fetch API를 이용하여 비동기 통신을 하는 예제입니다. thencatch를 통해 Promise 객체로 반환된 값을 받아볼 수 있습니다.

 

비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, Promise Chaining을 통해 여러 개의 Promise를 연결하여 사용함으로 Callback Hell 문제를 해결할 수 있습니다. 기본적으로 후속처리 메서드인 thencatch를 통해 사용할 수 있고 기존과 동일하게 Promise 객체를 반환합니다.

function getUserImage() {
  return new Promise({
    // ...
  })
};

...

getUser()
  .then((res) => {
    console.log(res)
    return getUserImage();
  })
  .then((res) => {
    console.log(res)
    return getUserFollower();
  })
  .then((res) => {
    console.log(res);
    return getUserComments();
  })
  .catch((err) => {
    console.error(err);
  });

위 코드는 사용자의 정보(프로필 이미지, 팔로워, 댓글)를 가져오는 Promise Chaining 코드입니다. 각 함수들은 Promise 객체를 반환하고 아래에서 getUser()를 실행시키면 then을 통해서 순서대로 호출됩니다.


Promise.all

Promise.allPromise가 담겨 있는 배열 등의 Iterable을 인자로 받고 전달받은 모든 Promise를 병렬로 처리하고 처리 결과를 resolve하는 새로운 Promise 객체를 반환합니다.
Promise.all([
  new Promise((resolve) => setTimeout(() => resolve(1), 3000)),
  new Promise((resolve) => setTimeout(() => resolve(2), 2000)),
  new Promise((resolve) => setTimeout(() => resolve(3), 1000)),
])
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  })
  
// (3) [1, 2, 3]

위에 설명했던 대로 병렬적으로 처리되기 때문에 해당 작업에 소요된 시간은 총 3초입니다. 배열에 들어있는 Promise 중에 하나라도 실패하면 가장 먼저 실패한 Promisereject한 에러를 reject하는 새로운 Promise 객체를 즉시 반환합니다.

 

배열 내 들어있는 요소가 Promise 객체가 아니더라도 Promise.all로 반환되는 객체는 Promise로 래핑 되어 반환됩니다.


Promise.allSettled

Promise.allSettledPromise가 담겨 있는 배열 등의 Iterable을 인자로 받고 전달받은 모든 Promise를 병렬로 처리하고 처리결과를 resolve하는 새로운 Promise 객체를 반환합니다.
Promise.allSettled([
  new Promise((resolve) => setTimeout(() => resolve(1), 3000)),
  new Promise((resolve, reject) => setTimeout(() => reject('Error..'), 2000)),
  new Promise((resolve) => setTimeout(() => resolve(3), 1000)),
])
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  })

// (3) [{...}, {...}, {...}]
// 0: {status: "fulfilled", value: 1}
// 1: {status: "rejected", reason: "Error.."}
// 2: {status: "fulfilled", value: 3}

✔ Promise.allSettledPromise.all은 동일한 구조를 가지고 있지만 다른 반환 결과를 보여줍니다. Promise.allSettledreject를 반환하는 Promise가 있으면 즉시 reject를 반환하지 않고 최종적으로 산출된 결과에서 fullfilled(성공적으로 이행된) 데이터와 값 그리고 reject된 데이터와 reject된 이유를 같이 보여줍니다.


Promise.race

Promise.racePromise.all과 동일하게 Promise가 담겨 있는 배열 등의 Iterable을 인자로 받으나 전달받은 모든 Promise들 중에서 가장 먼저 처리되어 resolve된 결과를 resolve하는 새로운 객체를 반환합니다.
Promise.race([
  new Promise((resolve) => setTimeout(() => resolve(1), 3000)),
  new Promise((resolve) => setTimeout(() => resolve(2), 2000)),
  new Promise((resolve) => setTimeout(() => resolve(3), 1000)),
])
  .then((response) => {
    console.log(res);
  })
  .catch((error) => {
  	console.error(error);
  });
// 3

위 코드를 실행시키면 1초가 걸린 3번째 Promise 객체가 반환한 resolve가 출력되어 나오게 됩니다. reject된 에러의 경우 동일하게 제일 먼저 rejectreject를 즉시 반환합니다.

 

참고

- MDN Mozilla <Promise>(developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise)

- 모던 자바스크립트 튜토리얼 <Promise>(ko.javascript.info/promise-api)