catch 메소드
- 프로미스 객체가
rejected
상태가 될 때 실행 하고 싶은 콜백은 then
메소드의 두 번째 파라미터로 넣으면 되는데 다른 방법도 있음
catch
메소드: 프로미스 객체가 rejected
상태가 되면 실행할 콜백을 등록하는 메소드
catch
메소드는 사실 then
메소드의 첫 번째 인자로 undefined을 넣은 것과 같음
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.catch((error) => { console.log(error); })
.then((result) => { console.log(result); });
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then(undefined, (error) => { console.log(error); })
.then((result) => { console.log(result); });
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');
})
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())
.then((result) => {
const feeds = result;
return processedFeeds;
})
.catch((error) => {
const storedGeneralNews = getStoredGeneralNews();
return storedGeneralNews;
})
.then((result) => { })
.catch((error) => { });
- 서버로부터 뉴스피드가 잘 조회되면 현재 코드에서 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) => {
fetch('https://second.com', (response) => {
fetch('https;//third.com', (response) => {
fetch('https;//fourth.com', (response) => {
});
});
});
});
fetch('https://first.com')
.then((response) => {
return fetch('https://second.com');
})
.then((response) => {
return fetch('https://third.com');
})
.then((response) => {
return fetch('https://third.com');
});
- callback hell 문제를 해결하고, 이에 더해서
- 비동기 작업 처리에 관한 좀 더 세밀한 처리를 자바스크립트 문법 단에서 해결하기 위해 등장했음
직접 만들어보는 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))
.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))
.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://~');
}
}
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);
});
p.then((result) => { console.log(result); });
setTimeout(() => { p.then((result) => { console.log(result); }); }, 5000);
여러 Promise 객체를 다루는 방법(심화)
all 메소드
- 여러 Promise 객체들이 있는 배열을 아규먼트로 받음
- 여러 Promise 객체의 작업 성공 결과를 기다렸다가 모두 한 번에 취합하기 위해서 사용함
- 하나의 Promise 객체라도 rejected 상태가 되면, 전체 작업이 실패한 것으로 간주해야 할 때 사용함
const p1 = fetch('https://learn.codeit.kr/api/members/1').then((res) => res.json());
const p2 = fetch('https://learn.codeit.kr/api/members/2').then((res) => res.json());
const p3 = fetch('https://learn.codeit.kr/api/members/3').then((res) => res.json());
Promise
.all([p1, p2, p3])
.then((results) => {
console.log(results);
});
- all 메소드도 then 메소드처럼 새로운 Promise 객체를 리턴함
- all 메소드는 이렇게 아규먼트로 들어온 배열 안에 있는 모든 Promise 객체가 pending 상태에서 fulfilled 상태가 될 때까지 기다림
- 모든 Promise 객체들이 fulfilled 상태가 되면, all 메소드가 리턴했던 Promise 객체는 fulfilled 상태가 되고, 각 Promise 객체의 작업 성공 결과들로 이루어진 배열을, 그 작업 성공 결과로 갖게 됨
- Promise 객체가 하나라도 rejected 상태가 되는 경우에 대비하려면 catch메소드를 이용하면 됨
const p1 = fetch('https://learn.codeit.kr/api/members/1').then((res) => res.json());
const p2 = fetch('https://learn.codeit.kr/api/members/2').then((res) => res.json());
const p3 = fetch('https://learnnnnnn.codeit.kr/api/members/3').then((res) => res.json());
Promise
.all([p1, p2, p3])
.then((results) => {
console.log(results);
})
.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);
})
.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);
})
.catch((value) => {
console.log(value);
});
allSettled 메소드
- 객체 배열을 아규먼트로 받고 Promise 객체를 리턴함
- 메소드가 리턴한 Promise 객체가 A라고 할 때, 배열 내의 모든 Promise 객체가 fulfilled 또는 rejected 상태가 되기까지 기다리고, pending 상태의 Promise 객체가 하나도 없게 되면, A의 상태값은 fulfilled 상태가 되고 그 작업 성공 결과로, 하나의 배열을 갖게됨
- 이 배열에는 아규먼트로 받았던 배열 내의 각 promise 객체의
- 최종 상태를 status 프로퍼티,
- 그 작업 성공 결과는 value 프로퍼티,
- 그 작업 실패 정보는 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)
- 업로드 시 진행 상태 정보를 얻을 수 있음
- 리퀘스트 취소 기능 지원
- 단점은 별도로 패키지를 다운로드해줘야 함