비동기를 이해하는 방법 - Promise

moreas·2023년 3월 10일
0

Javascript

목록 보기
2/6

Promise의 탄생

콜백의 단점- callback hell


Callback Hell

비동기 함수의 처리 결과에 대한 처리는 비동기 함수 내부에서 구현해야 하므로 함수의 매개변수로 콜백함수를 연속해서 사용하는 것을 말한다.
콜백을 연달아 사용하게 되면 코드의 가독성이 떨어진다.

firstFunc(args, function() {
  secondFunc(args, function() {
    thirdFunc(args, function() {
      // And so on...
    });
  });
});

Promise란?

비동기 처리 상태와 처리 결과를 관리하는 객체.
인자로 resolve, reject 함수를 받는다.

let istrue = true;
const promise = new Promise((resolve, reject) => {
	if(istrue) {
     	resolve('result')
    } else {
    	reject('error')}
});
console.log(promise)

프로미스는 3가지 중 하나의 상태를 가진다 :
- pending: 초기 상태. 이행되거나 거절되지 않은 대기 상태라고도 불림.
- fulfilled: 실행이 성공적으로 완료된 상태
- rejected: 실행이 실패된 상태


프로미스 후속 처리 메서드

  • 에러처리도 할 수 있다.

1. then

프로미스에 이행과 거부 처리기 콜백을 추가하고, 콜백이 호출될 경우 그 반환값으로 이행하며 호출되지 않을 경우(onFulfilled, onRejected 중 상태에 맞는 콜백이 함수가 아닐 경우) 처리된 값과 상태 그대로 처리되는 새로운 프로미스를 반환합니다.

2. catch

프로미스에 거부 처리기 콜백을 추가하고, 콜백이 호출될 경우 그 반환값으로 이행하며 호출되지 않을 경우, 즉 이전 프로미스가 이행하는 경우 이행한 값을 그대로 사용해 이행하는 새로운 프로미스를 반환합니다.

3. finally

프로미스의 이행과 거부 여부에 상관없이 처리될 경우 항상 호출되는 처리기 콜백을 추가하고, 이행한 값 그대로 이행하는 새로운 프로미스를 반환합니다.


const myFirstPromise = new Promise((resolve, reject) => {
  // 우리가 수행한 비동기 작업이 성공한 경우 resolve(...)를 호출하고, 
  // 실패한 경우 reject(...)를 호출합니다.
  // 이 예제에서는 setTimeout()을 사용해 비동기 코드를 흉내냅니다.
  // 실제로는 여기서 XHR이나 HTML5 API를 사용할 것입니다.
  setTimeout( function() {
    resolve("성공!")  // 와! 문제 없음!
  }, 250)
})

myFirstPromise.then((successMessage) => {
  // successMessage는 위에서 resolve(...) 호출에 제공한 값입니다.
  // 문자열이어야 하는 법은 없지만, 위에서 문자열을 줬으니 아마 문자열일 것입니다.
  console.log("와! " + successMessage)
});

4. 프로미스 체이닝

  • then, catch, finally는 promise의 추가적인 액션을 연관시키기 위해 사용할 수 있다.
    모두 프로미스를 반환하므로 체이닝하여 사용할 수 있다.
const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 300);
});

myPromise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB, handleRejectedB)
  .then(handleFulfilledC, handleRejectedC);
  .catch(handleRejectedAny);


// 화살표 함수 사용 

myPromise
  .then((value) => `${value} and bar`)
  .then((value) => `${value} and bar again`)
  .then((value) => `${value} and again`)
  .then((value) => `${value} and again`)
  .then((value) => {
    console.log(value);
  })
  .catch((err) => {
    console.error(err);
  });


프로미스 정적 메서드

1. Promise.resolve()

  • 주어진 값으로 이행하는 Promise 객체를 반환합니다. 이때 지정한 값이 then 가능한(then 메서드를 가지는) 값인 경우, Promise.resolve()가 반환하는 프로미스는 then 메서드를 "따라가서" 자신의 최종 상태를 결정합니다. 그 외의 경우, 반환된 프로미스는 주어진 값으로 이행합니다.

  • 어떤 값이 프로미스인지 아닌지 알 수 없는 경우, 보통 일일히 두 경우를 나눠서 처리하는 대신 Promise.resolve()로 값을 감싸서 항상 프로미스가 되도록 만든 후 작업하는 것이 좋습니다.

2. Promise.reject()

  • 주어진 사유로 거부하는 Promise 객체를 반환합니다.
- resolve reject는 이미 존재하는 값을 래핑하여 프로미스를 생성하기 위해 사용한다.
// 배열을 resolve하는 프로미스를 생성
const resolvedPromise = Promise.resolve([1,2,3]);
resolvedPromise.then(console.log);

// 배열을 reject하는 프로미스를 생성
const rejectPromise = Promise.reject(new Error('Error'));
rejectPromise.catch(console.log);

3. Promise.all()

  • 주어진 모든 프로미스가 이행하거나, 한 프로미스가 거부될 때까지 대기하는 새로운 프로미스를 반환합니다.

  • 반환하는 프로미스가 이행한다면, 매개변수로 제공한 프로미스 각각의 이행 값을 모두 모아놓은 배열로 이행합니다. 배열 요소의 순서는 매개변수에 지정한 프로미스의 순서를 유지합니다.

  • 반환하는 프로미스가 거부된다면, 매개변수의 프로미스 중 거부된 첫 프로미스의 사유를 그대로 사용합니다.

4. Promise.race()

  • 주어진 모든 프로미스 중 하나라도 처리될 때까지 대기하는 프로미스를 반환합니다.

  • 반환하는 프로미스가 이행한다면, 매개변수의 프로미스 중 첫 번째로 이행한 프로미스의 값으로 이행합니다.

  • 반환하는 프로미스가 거부된다면, 매개변수의 프로미스 중 거부된 첫 프로미스의 사유를 그대로 사용합니다.

5. Promise.allSettled()

  • 주어진 모든 프로미스가 처리(이행 또는 거부)될 때까지 대기하는 새로운 프로미스를 반환합니다.

  • Promise.allSettled()가 반환하는 프로미스는 매개변수로 제공한 모든 프로미스 각각의 상태와 값(또는 거부 사유)을 모아놓은 배열로 이행합니다.


async / await

프로미스를 기반으로 동작한다. 프로미스의 후속처리 메서드에 콜백 함수를 전달해서 비동기 처리 결과를 후속 처리할 필요 없이 마치 동기적인 함수처럼 프로미스를 처리할 수 있다.

  • await 키워드는 반드시 async 함수 내부에서 사용해야 한다.
  • 명시적으로 프로미스를 반환하지 않더라도 암묵적으로 반환값을 resolve하는 프로미스를 반환한다.
// async 
async function foo(n) {return n;}
foo(1)
.then(v => console.log(v)); // 1


const baz = async n => n;
baz(3).then(v => console.log(v)); // 3
  • await은 프로미스가 settled 상태 (비동기 처리가 수행된 상태)가 될 때까지 대기하다가 settled 상태가 되면 프로미스가 resolve한 처리 결과를 반환한다. await 키워드는 반드시 프로미스 앞에서 사용해야 한다.
const fetch = require('node-fetch');

const getUser = async id => {
  (1) const res = await fetch(`https://api.github.com/users/${id}`); 
  (2) const { name } = await res.json();
  console.log(name); 
};

getUser('aaa2');
  • (1)은 fetch함수가 수행한 HTTP 요청에 대한 서버의 응답이 도착하여 fetch가 반환한 프로미스가 settled 상태가 될 때까지 대기한다.
  • 이후 프로미스가 settled가 되면 처리한 결과가 res변수에 할당된다.

Fetch 함수 API

const promise = fetch(url [, optional])

HTTP 요청 전송 기능을 제공하는 클라이언트 사이드 Web API다.
XMLHttpRequest 객체보다 사용이 편리하고 프로미스를 지원하기 때문에 콜백의 단점에서 자유롭다. fetch 함수는 HTTP 응답을 나타내는 Response 객체를 래핑한 프로미스 객체를 반환한다

  • fetch는 404, 500의 서버 에러를 만났을 때 에러처리를 명확히 하지 못하는 경우가 있다. fetch 함수를 사용할 때는 resolve한 불리언 타입의 ok 상태를 확인해 명시적으로 에러를 처리하는 것을 권장한다.
fetch(url)
	.then(response => {
		if(!response.ok) throw new Error(response.statusText);
  		return response.json();
})

axios

Axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리

  • axios를 사용하면 모든 에러를 catch에서 처리할 수 있어 편리하다.
const axios = require('axios');

// 지정된 ID를 가진 유저에 대한 요청
axios.get('/user?ID=12345')
  .then(function (response) {
    // 성공 핸들링
    console.log(response);
  })
  .catch(function (error) {
    // 에러 핸들링
    console.log(error);
  })
  .finally(function () {
    // 항상 실행되는 영역
  });

// 선택적으로 위의 요청은 다음과 같이 수행될 수 있습니다.
axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })
  .finally(function () {
    // 항상 실행되는 영역
  });  

// async/await 사용을 원한다면, 함수 외부에 `async` 키워드를 추가하세요.
async function getUser() {
  try {
    const response = await axios.get('/user?ID=12345');
    console.log(response);
  } catch (error) {
    console.error(error);
  }
}


공부에 참고한 자료

모던 자바스크립트

profile
Everything is connected 🐶 좀 더 나은 개발을 위해

0개의 댓글