[JS Log] 프로미스

2023. 10. 7. 19:54Trip to JavaScript

비동기에 대한 내용을 쭉 다루고 있다. 비동기와 점점 가까워지는 느낌이다. 정말 개발을 처음 배울 때 Promise는 너무 어려운 존재였다. 자꾸 뭘 약속을 해준다는데 더 이해가 안되는 느낌이었다. 그냥 처리를 해줄게라고 약속하는 것이었다. async/await이 나와 쓸 일은 많이 없지만 처음이자 마지막으로 제대로 정리해보고 간다.

45.1 비동기 처리를 위한 콜백 패턴의 단점

45.1.1 콜백 헬

const get = url => {
  const xhr = new XMLHttpRequest()
  xhr.open('GET', url)
  xhr.send()

  xhr.onload = () => {
    if (xhr.status === 200) {
      console.log(JSON.parse(xhr.response))
      return JSON.parse(xhr.response)
    } else {
      console.log(`${xhr.status} ${xhr.statusText}`)
    }
  }
}

const response = get('url')
console.log(response)

> 책에서 Ajax를 예시로 들고 있다. onload 메서드는 콜백 함수로 결과값을 처리하고 있다. 여기서 문제는 return문으로 반환한 값을 아래 response가 받지 못한다는 것이다. 비동기 함수이기에 콜스택이 비어야 onload 콜백함수가 실행 될 테니 마지막 response는 계속 undefined를 받게 될 것이다. 

 

> 책은 이렇게 설명한다. 이처럼 비동기 함수는 비동기 처리 결과를 외부에 반환할 수 없고, 상위 스코프의 변수에 할당할 수도 없다. 따라서 비동기 함수의 처리 결과(서버의 응답 등)에 대한 후속 처리는 비동기 함수 내부에서 수행해야 한다.

 

> 이를 위해서 또 다른 비동기 함수를 받게 되는데 이 것이 이어지고 이어지다보면 콜백헬에 빠지게 되는 것이다.

 

45.1.2 에러 처리의 한계

> 책에서는 비동기 처리를 위한 콜백 패턴에서 에러 처리의 어려움을 이야기한다. 예시로 try catch문을 드는데 catch가 setTimeout에서 발생시킨 에러를 캐치하지 못한다. 사실 이 것도 비동기 처리를 위해 콜백 함수가 나중에 처리 되기에 생겨나는 문제이다. 근데 여기서 숫자가 왜 뜨는지 잘 모르겠다.

45.2 프로미스의 생성

> Promise가 ES6 이후 만들어졌고 표준 빌트인 객체로 인자도 2개의 함수를 받는다. reslove, reject로 에러 처리를 편리하게 해준다. 이전에 비동기 함수를 프로미스로 작성할 수 있다.

 

const promiseGet = url => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.send()
  
    xhr.onload = () => {
      if (xhr.status === 200) {
       resolve(JSON.parse(xhr.response))
      } else {
        reject(new Error(xhr.status))
      }
    }
  })
}

promiseGet('url')

> 사실 프로미스는 아래 그림으로 끝이 난다. 

> 아래는 MDN에서 가져온 이미지이다. then으로 계속 이어서 쓸 수 있는데 사실 이것도 콜백헬 같은 느낌을 줘서 다른 문법이 나오게 되었다. 그리고 프로미스 상태에 대해서 이야기하는데 성공하면 fulfilled 실패하면 rejected라고 뜬다.

>그리고 책은 한 마디로 프로미스를 정리한다. 프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체다.

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

> 프로미스의 비동기 처리 상태가 변화하면 후속 처리 메서드에 인수로 전달한 콜백 함수가 선택적으로 호출된다. 모든 후속 처리 메서드는 프로미스를 반환하며, 비동기로 동작한다. 프로미스의 후속 처리 메서드는 다음과 같다.

45.3.1 Promise.prototype.then

> then은 두 개의 인자를 받는데 성공 했을 때 콜백 함수와 실패 했을 때 콜백 함수이다.

45.3.2 Promise.prototype.catch

> catch는 실패 했을 때 콜백 함수만 받는다.

45.3.2 Promise.prototype.finally

> finally는 성공, 실패 무관하게 실행할 콜백 함수를 인자로 받는다.

45.4 프로미스의 에러 처리

> rejected의 경우 에러 처리를 catch로 할 것을 이야기하고 있다. catch는 내부적으로는 then(undefined, onRejected)를 호출한다고 한다. 이렇게 그대로 쓰면 가독성이 안좋고 무엇보다도 첫 번째 인자의 에러를 캐치하지 못한다. 결론적으로 에러 처리에서는 catch문을 써야한다.

promiseGet('url').then(res => cdsfjlk.log(res), err => console.log(err))

45.5 프로미스 체이닝

promiseGet('${url}/posts/1')
.then(({userId}) => {promiseGet('${url}/users/${userId}')})
.then(userInfo => console.log(userInfo))
.catch(err => console.log(err))

> 후속 처리 메서드를 통해서 이어지는 것을 프로미스 체이닝이라고 한다.

45.6 프로미스의 정적 메서드

> 후속 처리 메서드를 통해서 이어지는 것을 프로미스 체이닝이라고 한다.

45.6.1 Promise.resolve / Promise.rejected

> 이미 존재하는 값을 프로미스로 만들어줌

45.6.2 Promise.all

> 여러개의 비동기 처리를 병렬 처리 할 때 사용

45.3.3 Promise.race

> 가장 먼저 fulfilled 상태가 된 프로미스의 처리 결과를 resolve하는 새로운 프로미스를 반환, 하나라도 reject이되면 에러를 reject하는 새로운 프로미스를 반환 

45.6.4 Promise.allSettled

> 여러개의 프로미스가 비동기 처리가 끝나면 상태를 배열 형태로 반환

45.7 마이크로태스크 큐

setTimeout(() => console.log(1), 0);

Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(3));

> 지금까지 배운 내용으로 생각하면 1, 2, 3이 나오리라 예상하지만 놀랍게도 그렇지 않다.

> 그 이유는 프로미스의 후속 처리 메서드의 콜백 함수느느 태스크 큐가 아니라 마이크로태스크 큐에 저장되기 때문이다. 마이크로태스크 큐는 태스크 큐와는 별도의 큐다. 마이크로태스크 큐에는 프로미스의 후속 처리 메서드의 콜백 함수가 일시 저장된다. 콜백 함수나 이벤트 핸들러를 일시 저장한다는 점에서 태스크 큐와 동일하지만 마이크로태스크 큐는 태스큐보다 우선순위가 높다. 

45.7 fetch

> fetch 함수는 HTTP 응답을 나타내는 Response 객체를 래핑한 Promise 객체를 반환한다. 

const wrongUrl = "바보";

fetch(wrongUrl)
  .then(() => console.log("ok"))
  .catch(() => console.log("error"));

> fetch 함수는 HTTP 에러가 발생해도 에러를 rejected 하지 않고 불리언 타입의 ok 상태를 false로 설정한 Response 객체를 resolve한다. 오프라인 등의 네트워크 장애나 CORS 에러에 의해 요청이 완려되지 못한 경우에만 프로미스를 reject한다.

const wrongUrl = "바보";

fetch(wrongUrl)
  .then((response) => {
    if (!response.ok) throw new Error(response.statusText);
    return response.json();
  })
  .then((todo) => console.log(todo))
  .catch((err) => console.log(err));

> ok가 아닌 상태를 명시해줘라 에러를 제대로 뱉어낸다. axios는 HTTP 에러를 에러 처리를 reject하는 프로미스를 반환하기에 axios를 쓰는게 좋을 것 같다.

 

 

오랫동안 미뤄왔던 Promise를 정리해 보았다. 한결 마음이 편하다. 

'Trip to JavaScript' 카테고리의 다른 글

[JS Log] 배열  (1) 2023.10.08
[JS Log] ES6 함수의 추가 기능  (1) 2023.10.08
[JS Log] Ajax  (0) 2023.10.07
[JS Log] 비동기 프로그래밍  (1) 2023.10.07
[JS Log] 좀 더 클로저  (0) 2023.10.06