https://codesmoothie.tistory.com/4
[JavaScript] 프로미스(Promise) Consume - fulfilled 처리 방법
fetch 함수가 반환하는 Promise를 어떻게 사용(Consume)하는지 방법을 알아보자. 아래는 오픈 API를 사용해 국가(country) 데이터를 가져오는 코드이다. 처음 fetch로 요청했을 때 Promise는 'pending' 상태이다.
codesmoothie.tistory.com
이 글은 "프로미스(Promise) - fulfilled 처리 방법"의 포스팅에서 연장되는 글이다. 아래의 코드는 지난번 작성한 코드이다.
const getCountryData = function (country) {
fetch(`https://restcountries.com/v2/name/${country}`)
.then((response) => response.json())
.then((data) => {
renderCountry(data[0]);
const neighbour = data[0].borders?.[0];
if (!neighbour) return;
return fetch(`https://restcountries.com/v2/alpha/${neighbour}`);
})
.then((response) => response.json())
.then((data) => renderCountry(data, 'neighbour'));
};
getCountryData('korea');
지난번 포스팅에서 Promise의 Consume에서 성공(fulfilled)한 경우를 살펴보았다. 이번에는 Promise가 거절(rejected)된 경우의 처리방법에 대해 알아본다. 먼저, 인위적으로 Promise를 rejected하는 방법을 알아보자.
인위적으로 Promise rejected를 만드는 방법
fetch의 Promise가 거부(rejceted)되는 경우는 오직 사용자의 인터넷이 연결되지 않는 경우에 발생한다. 이러한 상황을 만들기 위해서는 크롬 개발자 도구의 'Network' 탭에서 Offline 설정을 해주면 된다. 설정 이후 'Console' 탭에서 새로고침을 하면 아래의 이미지와 같은 에러가 나타난다.
첫번째 라인의 에러는 인터넷이 연결되어 있지 않다는 메세지이다. 두번째 라인의 에러는 Promise내에 Uncaught(잡히지 않은) 에러가 있다는 것이며, 해당 에러를 어떻게 처리하는지 알아보자.
Promise rejected 처리 방법
then 메소드의 두번째 인자에 아래와 같이 콜백 함수를 추가하여 rejected된 error를 처리할 수 있다.
fetch(`https://restcountries.com/v2/name/${country}`)
.then((response) => response.json(), (err) => alert(err)) // error를 alert로 처리
Console을 확인해보면 에러 메세지가 없어진 것을 알 수 있다. 이는 첫번째 fetch에서 error가 처리되는 순간 코드 처리가 종료되었다는 것을 알려준다.
그런데 첫번째 fetch가 fulfilled 되었는데 두번째 fetch에서 rejected되는 경우도 존재할 수 있다. 따라서, then 메소드 내에서 error 처리 방식은 fetch가 속한 두번째 then 메소드에도 error 처리 코드를 한번 더 작성해야한다. 하지만, 매번 이러한 코드를 추가하는 것은 매우 번거로운 일이기 때문에 아래와 같이 'catch' 메소드를 통해 한번에 처리하는 방식이 존재한다. Promise Chaining 끝에 존재하는 'catch' 메소드는 어디서 error가 발생하든 모두 처리해준다. 이때 콜백함수에 반환되는 error는 Javascript 객체이며, message 속성을 가지고 있다.
const getCountryData = function (country) {
fetch(`https://restcountries.com/v2/name/${country}`)
.then((response) => response.json())
.then((data) => {
renderCountry(data[0]);
const neighbour = data[0].borders?.[0];
if (!neighbour) return;
return fetch(`https://restcountries.com/v2/alpha/${neighbour}`);
})
.then((response) => response.json())
.then((data) => renderCountry(data, 'neighbour'))
.catch((err) => {
console.error(err);
renderError(`Something went wrong ${err.message}. Try again!`);
});
};
const renderError = function (msg) {
countriesContainer.insertAdjacentText('beforeend', msg);
countriesContainer.style.opacity = 1;
};
에러를 처리할 때는 'alert' 띄우거나 'console.error'를 사용하는 등 다양한 방식이 존재한다. 'console.error'의 장점은 'Stack trace'로 에러가 정확하게 어디서 발생하는지 보여준다는 점이다. 하지만, Console 메세지만 출력하는 것보다는 상기 코드와 같이 함수내의 Dom을 가져와 UI상에서 error 메세지를 확인할 수 있도록 처리하는 것이 좋다.
finally 메소드 사용법
'then'과 'catch' 이외에 'finally' 메소드가 있다. finally에 정의한 콜백함수는 Promise가 fulfilled 또는 rejected되든 상관없이 항상 호출된다. 마지막 정리를 하면 then 메소드는 fulfilled 될때, catch는 rejected될 때, finally는 항상 호출된다. finally를 사용하는 좋은 예로는 로딩를 숨기는 것이다. 참고로 finally 메소드는 Promise에서만 작동한다. 즉, catch 메소드도 then과 마찬가지로 Promise를 반환하는 것이다.
const getCountryData = function (country) {
fetch(`https://restcountries.com/v2/name/${country}`)
.then((response) => response.json())
.then((data) => {
renderCountry(data[0]);
const neighbour = data[0].borders?.[0];
if (!neighbour) return;
return fetch(`https://restcountries.com/v2/alpha/${neighbour}`);
})
.then((response) => response.json())
.then((data) => renderCountry(data, 'neighbour'))
.catch((err) => {
console.error(`${err} 🥲🥲🥲`);
renderError(`Something went wrong 🥲🥲 ${err.message}. Try again `);
})
.finally(() => {
countriesContainer.style.opacity = 1;
});
};
초보자들이 많이 하는 실수
getCountryData('sdlkfmsd'); // 국가명이 아닌경우
상기 코드와 같이 존재하지 않는 나라의 경우 API가 어떠한 결과도 찾지 못하여, Console 탭에 아래와 같은 에러가 나타난다.
'console.error' 코드가 존재하는 276번째 라인에 정의되지 않은 프로퍼티를 읽을 수 없다는 에러 메세지가 나타난다. 아래의 코드의 'data.flag'와 함께 보면 해당 에러 메세지가 나타난 이유를 이해할 수 있다. 이 경우 큰 도움은 되지 않지만, 가끔 Stack trace를 살펴보면 문제 해결에 도움이 될 때가 있다.
const renderCountry = function (data, className = '') {
const html = `<article class="country ${className}">
<img class="country__img" src="${data.flag}" />
<div class="country__data">
<h3 class="country__name">${data.name}</h3>
<h4 class="country__region">${data.region}</h4>
<p class="country__row"><span>👫</span>${(
+data.population / 1000000
).toFixed(1)}</p>
<p class="country__row"><span>🗣️</span>${data.languages[0].name}</p>
<p class="country__row"><span>💰</span>${data.currencies[0].name}</p>
</div>
</article>`;
countriesContainer.insertAdjacentHTML('beforeend', html);
countriesContainer.style.opacity = 1;
};
위에서 'flag'라는 데이터를 읽을 수 없다고 메세지가 나타나는 것을 확인했다. 하지만, 실제로는 API에서 해당 국가의 이름을 찾을 수 없다는 것이 근본적인 원인이다. 우리는 'Status code 404'를 통해 사전에 이를 알 수 있다. '404'는 진짜 오류는 아니지만, 그것과 비슷한 느낌이라고 생각하면 된다.
앞서 fetch Promise가 rejected되는 경우는 오로지 인터넷 연결이 없을 때라고 설명했다. 따라서 fetch Promise는 API가 존재하지 않는다는 사실은 'rejected'하지 않고 'fulfilled'로 실행되며 catch는 이를 감지하지 않는다. 결국, catch는 'flag'라는 데이터를 읽을 때 감지되어 해당 데이터가 존재하지 않는다는 사실만을 알려준다. 즉, API를 찾지 못하는 문제가 분명히 존재하지만 fetch 메소드는 이를 'rejected'하지 않는다는 것이다.
API를 찾지 못한 경우, 에러 처리 방법
많은 사람들이 API를 찾지 못한 경우 Promise가 'rejected'될 것이라고 생각하지만, 그렇지 않다는 사실을 알아 보았다. 그렇다면 어떻게 이런 상황을 처리할 수 있을까? 수동으로만 처리 가능하다.
fetch에서 반환된 값을 json 메소드로 가공하기 전에 내부의 값을 살펴보면 'ok', 'status' 프로퍼티가 존재한다. 이를 활용하여 API를 가져오기에 실패한 경우에는 'ok' 프로퍼티가 false라는 사실을 이용하여, Primise를 'rejected'를 만들 수 있다.
const getCountryData = function (country) {
fetch(`https://restcountries.com/v2/name/${country}`)
.then((response) => {
if (!response.ok)
// 새로운 에러를 생성하기 위해 'new Error'를 사용
// 'throw'를 통해 현재 함수를 즉시 종료
throw new Error(`Country not found ${response.status} `);
return response.json();
})
.then((data) => {
renderCountry(data[0]);
const neighbour = data[0].borders?.[0];
if (!neighbour) return;
return fetch(`https://restcountries.com/v2/alpha/${neighbour}`);
})
.then((response) => {
if (!response.ok)
throw new Error(`Country not found ${response.status} `);
return response.json();
})
.then((data) => renderCountry(data, 'neighbour'))
.catch((err) => {
console.error(`${err} 🥲🥲🥲`);
renderError(`Something went wrong 🥲🥲 ${err.message}. Try again `);
})
.finally(() => {
countriesContainer.style.opacity = 1;
});
};
getCountryData1('alskdmlkasmd')
'new Error'는 새로운 에러를 생성하며, 'throw'는 return과 같이 함수를 즉시 종료 한다. 'throw new Error'를 사용하면 Promise가 즉시 'rejected'되며, 에러 값은 catch로 전달 된다.
하지만, 상기의 코드는 중복이 생겨난다. 따라서 일반적으로 아래와 같이 fetch 함수를 따로 정의하여 사용한다. 'getJSON' 함수를 fetch Promise를 반환한다. 이웃 나라가 없는 경우를 처리하기 위한 처리 구문이 추가되었다. then 메소드안에서 error가 발생하면 즉시 'rejected'된다는 사실을 기억하자.
const getJSON = function (url, errorMsg = 'Something went wrong') {
return fetch(url).then((response) => {
if (!response.ok) {
throw new Error(`${errorMsg} ${response.status}`);
}
return response.json();
});
};
const getCountryData = function (country) {
getJSON(`https://restcountries.com/v2/name/${country}`, 'Country not found')
.then((data) => {
renderCountry(data[0]);
const neighbour = data[0].borders[0];
// 이웃 나라가 없는 경우 처리
if (!neighbour) throw new Error('No neighbour found!');
return getJSON(
`https://restcountries.com/v2/alpha/${neighbour}`,
'Country not found'
); // So always return to promise
})
.then((data) => renderCountry(data, 'neighbour'))
.catch((err) => {
console.error(`${err} 🥲🥲🥲`);
renderError(`Something went wrong 🥲🥲 ${err.message}. Try again `);
})
.finally(() => {
countriesContainer.style.opacity = 1;
});
};
const renderError = function (msg) {
countriesContainer.insertAdjacentText('beforeend', msg);
countriesContainer.style.opacity = 1;
};
'Javascript' 카테고리의 다른 글
[JavaScript] 클로저(Closures) - 개념편 (0) | 2024.01.02 |
---|---|
[JavaScript] 비동기 코드의 동작 원리 - 이벤트 루프(The Event Loop) (2) | 2023.12.29 |
[JavaScript] AJAX와 API란 무엇인가? - 비동기 코드에 대한 이해 (0) | 2023.12.03 |
[JavaScript] 프로미스(Promise) Consume - fulfilled 처리 방법 (0) | 2023.11.25 |
[JavaScript] 프로미스(Promise)란 - 개념편 (0) | 2023.11.12 |