[강의] 비동기 실행과 Promise 객체 2

김하은·2023년 11월 15일
0

코드잇 강의 정리

목록 보기
43/60

catch 메소드

  • 프로미스 객체가 rejected 상태가 될 때 실행 하고 싶은 콜백은 then메소드의 두 번째 파라미터로 넣으면 되는데 다른 방법도 있음
  • catch메소드: 프로미스 객체가 rejected 상태가 되면 실행할 콜백을 등록하는 메소드
  • catch 메소드는 사실 then 메소드의 첫 번째 인자로 undefined을 넣은 것과 같음
// Internet Disconnected

fetch('https://jsonplaceholder.typicode.com/users')
  .then((response) => response.text())
  .catch((error) => { console.log(error); })
  .then((result) => { console.log(result); });
// Internet Disconnected

fetch('https://jsonplaceholder.typicode.com/users') // Promise-A
  .then((response) => response.text()) // Promise-B
  .then(undefined, (error) => { console.log(error); }) // Promise-C
  .then((result) => { console.log(result); }); // Promise-D

catch 메소드는 마지막에 씁니다

  • 프로미스 체이닝에서 어느 프로미스 객체가 rejected 상태가 되더라도 잘 대응하기 위해서는 catch 메소드를 가장 마지막에 써 주는 것이 좋음
  • rejected상태의 프로미스만 남고 딱히 어떤 처리를 해주지 않으면 웹 브라우저는 에러로 인식함
  • 아래 경우에서 젤 마지막에 발생한 에러는 잡지 못함
fetch('https://jsonplaceholder.typicode.com/users')
  .then((response) => response.text())
  .catch((error) => { console.log(error); })
  .then((result) => { 
  	console.log(result);
  	throw new Error('test');
  })

// catch 메소드 젤 아래로 내리기
fetch('https://www.google.com')
  .then((response) => response.text())
  .then((result) => { 
  	console.log(result);
  	throw new Error('test');
  })
  .catch((error) => { console.log(error); })


catch 메소드를 여러 개 쓰는 경우

  • catch 메소드를 마지막뿐만 아니라 Promise Chain 중간중간에 쓰는 경우도 존재함
  • 만약 중간에 에러가 발생해도 catch 메소드가 그 대안을 뒤로 넘겨줄 수 있으면 catch 메소드를 중간에 써도 됨
  • 어떤 SNS 웹 사이트에서 나에게 최적화된 뉴스피드(newsfeed)를 보여주는 코드가 있다고 했을 때
fetch('https://friendbook.com/my/newsfeeds')
  .then((response) => response.json()) // -- A
  .then((result) => { // -- B
    const feeds = result;
    // 피드 데이터 가공...
    return processedFeeds; 
  })
  .catch((error) => { // -- C
    // 미리 저장해둔 일반 뉴스를 보여주기  
    const storedGeneralNews = getStoredGeneralNews();
    return storedGeneralNews;
  })
  .then((result) => { /* 화면에 표시 */ }) // -- D
  .catch((error) => { /* 에러 로깅 */ }); // -- E
  • 서버로부터 뉴스피드가 잘 조회되면 현재 코드에서 A, B, D 줄에 있는 콜백들이 잘 실행됨
  • fetch 함수의 작업이 실패하면 C 줄의 콜백이 실행됨
    • C줄의 콜백은 만일을 대비해 저장해둔 일반 뉴스 데이터를 그대로 가져오는 기능을 함
    • 이렇게 되면 인터넷이 안 되는 상황에서도 나만을 위한 최적화된 뉴스피드는 못 보지만 일반적인 세상 뉴스는 사용자가 볼 수 있음
  • 이렇게 Promise Chain 중에서 비록 에러가 발생했다고 해도 만약 실패한 작업 대신 다른 방법을 통해서 작업을 정상적으로 끝마칠 수 있는 상황이라면 catch 메소드를 중간에 사용하기도 함
  • 하나의 작업이라도 실패하면 전체 작업이 실패했다고 봐도 되는 경우: Promise Chain 마지막에만 catch 메소드를 써주면 됨
  • 에러가 발생하더라도 다른 방식으로 복구해서 살려낼 방법이 있는 경우: catch 메소드 안의 콜백에서 그런 복구 작업을 해주면 됨

finally 메소드

  • 어떤 작업의 성공 여부, 즉 프로미스 객체가 fulfilled 상태가 되든 rejected 상태가 되든 상관 없이 항상 실행하고 싶은 콜백이 있을 때 사용함
  • 작업 성공 결과나 작업 실패 정보가 필요하지 않기 때문에 파라미터가 필요 없음
  • finally는 정상적인 경우든 최악의 경우든 상관없이 항상 무조건 실행해야하는 코드가 있을 때 실행함
    • 프로미스 체이닝에서 작업을 수행하기 위해서 사용했던 자원 정리
    • 로그 기록 남기기
    • 항상 특정 변수의 값을 변경해줘야 할 때
  • 보통 catch 메소드 바로 뒤에 씀
fetch('https://www.google.com')
  .then((response) => response.text())
  .then((result) => { console.log(result); })
  .catch((error) => { console.log(error); })
  .finally(() => { console.log('exit'); });

Promise 객체는 왜 등장했을까?

fetch('https://first.com', callback)
  • fetch 함수를 이런 식으로 사용하지 않고 Promise 객체 문법이 도입된 이유: 함수에 콜백을 직접 넣는 형식은 콜백 헬(callback hell)이라고 하는 문제를 일으킬 수도 있기 때문임
  • 콜백 헬: 가독성이 떨어지는 현상
// 콜백 헬
fetch('https://first.com', (response) => {
  // Do Something
  fetch('https://second.com', (response) => {
    // Do Something
    fetch('https;//third.com', (response) => {
      // Do Something
      fetch('https;//fourth.com', (response) => {
        // Do Something
      });
    });
  });
});

// fetch 이용
fetch('https://first.com')
  .then((response) => {
    // Do Something 
    return fetch('https://second.com');
  })
  .then((response) => {
    // Do Something 
    return fetch('https://third.com');
  })
  .then((response) => { 
    // Do Something 
    return fetch('https://third.com');
  });
  1. callback hell 문제를 해결하고, 이에 더해서
  2. 비동기 작업 처리에 관한 좀 더 세밀한 처리를 자바스크립트 문법 단에서 해결하기 위해 등장했음

직접 만들어보는 Promise 객체

  • new를 붙이면 자바스크립트에서 새로운 객체를 생성할 수 있음
  • new Promise: 프로미스 객체 생성
  • resolve, reject: 프로미스 객체가 생성될 때 자동으로 실행되는 함수로 executor 함수라고 함
    • resolve파라미터: 생성될 프로미스 객체를 fulfilled 상태로 만들 수 있는 함수가 연결됨
    • reject파라미터: 생성될 프로미스 객체를 rejected 상태로 만들 수 있는 함수가 연결됨
  • 즉, 아래 코드를 해석하면 p라는 프로미스 객체가 2초 후에 fulfilled 상태가 된다는 뜻이고 'success' 문자열이 작업 성공 결과가 됨
...
const p = new Promise((resolve, reject) => {
    setTimeout(() => { resolve('success'); }, 2000);
  });

p.then((result) => { console.log(result); });
...

...
const p = new Promise((resolve, reject) => {
    setTimeout(() => { reject(new Error('fail')); }, 2000);
  });

p.catch((error) => { console.log(error); });
...


Promisify

  • Promise 객체를 직접 만드는 이유: 전통적인 형식의 비동기 실행 함수를 사용하는 코드를, Promise 기반의 코드로 변환하기 위해서

setTimeout 함수 예시

  • wait 함수: 특정 밀리세컨즈만큼 시간이 지난 후에 text 파라미터로 전달받은 값을 리턴하는 함수
function wait(text, milliseconds) {
  setTimeout(() => text, milliseconds);
}

// 
function wait(text, milliseconds) {
  setTimeout(() => text, milliseconds);
}

fetch('https://jsonplaceholder.typicode.com/users')
  .then((response) => response.text())
  .then((result) => wait(`${result} by Codeit`, 2000)) // 2초 후에 리스폰스의 내용 뒤에 'by Codeit' 추가하고 리턴
  .then((result) => { console.log(result); });

  • Promise Chaining 안에서 이렇게 비동기 실행되는 함수를 바로 사용하면, 나중에 실행되는 부분의 리턴값(여기서는 text)를 Promise Chain에서 사용할 수 없게 됨
function wait(text, milliseconds) {
  const p = new Promise((resolve, reject) => {
    setTimeout(() => { resolve(text); }, 2000);
  });
  return p;
}

fetch('https://jsonplaceholder.typicode.com/users')
  .then((response) => response.text())
  .then((result) => wait(`${result} by Codeit`, 2000)) // 2초 후에 리스폰스의 내용 뒤에 'by Codeit' 추가하고 리턴
  .then((result) => { console.log(result); });

  • 기존의 비동기 실행 함수(여기서는 setTimeout)의 콜백이 리턴하는 값을 Promise Chain에서 사용하고 싶다면, 해당 함수를 감싸서 Promise 객체를 직접 생성하는 코드를 작성해야 하고 그 Promise 객체를 리턴해야 위 예시처럼 Promise Chain에서 해당 리턴값을 받아서 사용할 수 있음
  • Promisify: 전통적인 형식의 비동기 실행 함수를 Promise 객체로 감싸서 그 Promise 객체를 리턴하는 형식으로 만드는 작업

콜백 헬(callback hell)과 Promise

  • 전통적인 형식의 비동기 실행 함수를 Promisify해서 콜백 헬을 방지하고, 가독성 높은 코드를 작성할 수 있음

Promisify를 하면 안 되는 함수들도 있습니다

  • 그 콜백을 한번만 실행하는 것들(setTimeout, readFile 등)만 Promisify해서 사용해도 됨
  • 콜백을 여러 번 실행하는 함수들(setInterval, addEventListener 등)인 경우에는 이렇게 Promisify하면 안 됨
  • Promise 객체는 한번 pending 상태에서 fulfilled 또는 rejected 상태가 되고나면 그 뒤로는 그 상태와 결과가 바뀌지 않기 때문에

이미 상태가 결정된 Promise 객체

  • 처음부터 바로 fulfilled 상태이거나 rejected 상태인 Promise 객체를 만드는 것도 가능함

이미 상태가 결정된 Promise 객체 만들기

  • new 생성자와 executor 함수를 사용하는 것 말고도 resolve 메소드나, reject 메소드를 사용하는 방법도 있음

fulfilled 상태의 Promise 객체 만들기

  • Promise의 resolve라는 메소드를 사용하면 바로 fulfilled 상태의 Promise 객체를 만들 수 있음
  • 위와 같이 쓰면 fulfilled 상태이면서, 작업 성공 결과로 문자열 'success'를 가진 Promise 객체를 만들 수 있음
const p = Promise.resolve('success');
p.then((result) => { console.log(result); }, (error) => { console.log(error); });

rejected 상태의 Promise 객체 만들기

  • Promise의 reject라는 메소드를 사용하면 바로 rejected 상태의 Promise 객체를 만들 수 있음
  • 위와 같이 쓰면 rejected 상태이면서, 작업 실패 정보로, fail이라는 메시지를 가진 Error 객체를 가진 Promise 객체를 만들 수 있음
const p = Promise.reject(new Error('fail'));
p.then((result) => { console.log(result); }, (error) => { console.log(error); });

함수 안에서 리턴하는 값이 여러 개인 경우 모든 리턴값을 Promise 객체로 통일하고 싶을 때 사용함

function doSomething(a, b) {
    //~~
  if (problem) {
    throw new Error('Failed due to..'));
  } else {
    return fetch('https://~');
  }
}

//문제가 존재하는 경우에도 Promise 객체를 리턴하고 싶다면
function doSomething(a, b) {
  // ~~
  if (problem) {
    return Promise.reject(new Error('Failed due to..'));
  } else {
    return fetch('https://~');
  }
}

Promise 객체의 작업 성공 결과 또는 작업 실패 정보

  • 이미 fulfilled 또는 rejected 상태가 결정된 Promise 객체라도 then 메소드를 붙이면, 콜백에서 해당 작업 성공 결과 또는 작업 실패 정보를 받아올 수 있음
  • Promise 객체는 항상 결과를 줄 수 있는 공급자(Provider)이고 그것의 then 메소드는 그 결과를 소비하는 콜백인 소비자(Consumer)를 설정하는 메소드로 시점과 연관이 없음
const p = new Promise((resolve, reject) => {
  setTimeout(() => { resolve('success'); }, 2000); // 2초 후에 fulfilled 상태가 됨
});

p.then((result) => { console.log(result); }); // Promise 객체가 pending 상태일 때 콜백 등록
setTimeout(() => { p.then((result) => { console.log(result); }); }, 5000); // Promise 객체가 fulfilled 상태가 되고 나서 콜백 등록 

여러 Promise 객체를 다루는 방법(심화)

all 메소드

  • 여러 Promise 객체들이 있는 배열을 아규먼트로 받음
  • 여러 Promise 객체의 작업 성공 결과를 기다렸다가 모두 한 번에 취합하기 위해서 사용함
  • 하나의 Promise 객체라도 rejected 상태가 되면, 전체 작업이 실패한 것으로 간주해야 할 때 사용함
// 1번 직원 정보
const p1 = fetch('https://learn.codeit.kr/api/members/1').then((res) => res.json());
// 2번 직원 정보
const p2 = fetch('https://learn.codeit.kr/api/members/2').then((res) => res.json());
// 3번 직원 정보
const p3 = fetch('https://learn.codeit.kr/api/members/3').then((res) => res.json());

Promise
  .all([p1, p2, p3])
  .then((results) => {
    console.log(results); // Array : [1번 직원 정보, 2번 직원 정보, 3번 직원 정보]
  });
  • all 메소드도 then 메소드처럼 새로운 Promise 객체를 리턴함
  • all 메소드는 이렇게 아규먼트로 들어온 배열 안에 있는 모든 Promise 객체가 pending 상태에서 fulfilled 상태가 될 때까지 기다림
  • 모든 Promise 객체들이 fulfilled 상태가 되면, all 메소드가 리턴했던 Promise 객체는 fulfilled 상태가 되고, 각 Promise 객체의 작업 성공 결과들로 이루어진 배열을, 그 작업 성공 결과로 갖게 됨
  • Promise 객체가 하나라도 rejected 상태가 되는 경우에 대비하려면 catch메소드를 이용하면 됨
// 1번 직원 정보
const p1 = fetch('https://learn.codeit.kr/api/members/1').then((res) => res.json());
// 2번 직원 정보
const p2 = fetch('https://learn.codeit.kr/api/members/2').then((res) => res.json());
// 3번 직원 정보
const p3 = fetch('https://learnnnnnn.codeit.kr/api/members/3').then((res) => res.json());

Promise
  .all([p1, p2, p3])
  .then((results) => {
    console.log(results); // Array : [1번 직원 정보, 2번 직원 정보, 3번 직원 정보]
  })
  .catch((error) => {
    console.log(error);
  });

race 메소드

  • 여러 Promise 객체들이 있는 배열을 아규먼트로 받음
  • all 메소드처럼 Promise 객체를 리턴하지만 그 적용 원리가 다름
  • 아규먼트로 들어온 배열의 여러 Promise 객체들 중에서 가장 먼저 fulfilled 상태 또는 rejected 상태가 된 Promise 객체와 동일한 상태와 결과를 갖게 됨
  • 여러 Promise 객체들을 레이스(race, 경쟁)시켜서 가장 빨리 상태가 결정된 Promise 객체를 선택하는 메소드임
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Success'), 1000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('fail')), 2000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('fail2')), 4000);
});

Promise
  .race([p1, p2, p3])
  .then((result) => {
    console.log(result); // Success 출력
  })
  .catch((value) => {
    console.log(value);
  });

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Success'), 6000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('fail')), 2000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('fail2')), 4000);
});

Promise
  .race([p1, p2, p3])
  .then((result) => {
    console.log(result); // Success 출력
  })
  .catch((value) => {
    console.log(value);
  });

allSettled 메소드

  • 객체 배열을 아규먼트로 받고 Promise 객체를 리턴함
  • 메소드가 리턴한 Promise 객체가 A라고 할 때, 배열 내의 모든 Promise 객체가 fulfilled 또는 rejected 상태가 되기까지 기다리고, pending 상태의 Promise 객체가 하나도 없게 되면, A의 상태값은 fulfilled 상태가 되고 그 작업 성공 결과로, 하나의 배열을 갖게됨
  • 이 배열에는 아규먼트로 받았던 배열 내의 각 promise 객체의
    1. 최종 상태를 status 프로퍼티,
    2. 그 작업 성공 결과는 value 프로퍼티,
    3. 그 작업 실패 정보는 reason 프로퍼티
      에 담은 객체들이 요소로 존재합니다.
[
   {status: "fulfilled", value: 1},
   {status: "fulfilled", value: 2},
   {status: "fulfilled", value: 3},
   {status: "rejected",  reason: Error: an error}
]
  • fulfilled 상태와 rejected 상태를 묶어서 settled 상태라고 하는데 allSettled 메소드는 말 그대로 배열 속 Promise 객체들이 settled 상태가 되기만 하면 됨

any 메소드

  • 객체 배열을 아규먼트로 받고 Promise 객체를 리턴함
  • 여러 Promise 객체들 중에서 가장 먼저 fulfilled 상태가 된 Promise 객체의 상태와 결과가 A에도 똑같이 반영됨
  • 만약 모든 Promise 객체가 rejected 상태가 되어버리면 AggregateError라고 하는 에러를 작업 실패 정보로 갖고 rejected 상태가 됨
  • any라는 단어의 뜻처럼 배열 속의 Promise 객체 중 단 하나라도 fulfilled 상태가 되면 됨

axios

  • axios 외부 패키지를 사용하면 fetch 함수 말고도 Ajax 통신을 할 수 있음
axios
  .get('https://jsonplaceholder.typicode.com/users')
  .then((response) => {
    console.log(response);
  })
  .catch((error) => {
    console.log(error);
  });
  • axios 객체에서 리퀘스트를 보내는 많은 메소드들이 fetch 함수처럼 Promise 객체를 리턴함
  • fetch 함수와 비슷한 점이 많음
  • axios 객체에는 fetch 함수에는 없는 다음과 같은 몇 가지 기능 및 장점들이 있음
    • 모든 리퀘스트, 리스폰스에 대한 공통 설정 및 공통된 전처리 함수 삽입 가능
    • serialization, deserialization을 자동으로 수행
    • 특정 리퀘스트에 대해 얼마나 오랫동안 리스폰스가 오지 않으면 리퀘스트를 취소할지 설정 가능(request timeout)
    • 업로드 시 진행 상태 정보를 얻을 수 있음
    • 리퀘스트 취소 기능 지원
  • 단점은 별도로 패키지를 다운로드해줘야 함
profile
아이디어와 구현을 좋아합니다!

0개의 댓글