저같은 경우 비동기 함수 처리를 하다보면 의도한대로 나오지 않는 경우가 허다합니다. 그러면 항상 조건문을 걸고 콜백함수 달기 바빴죠..
const 요청하기 = function() {
const xhr = new XMLHttpRequest();
if (xhr.status === 200) {
successCallback();
} else {
failedCallback();
}
}
이런 코드가 하나둘 쌓이다 보면 어느새 암호문 해독수준인 코드가 눈앞에 펼쳐질 겁니다.
const 요청하기 = function() {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
function successCallback(JSON.parse(xhr.data)) {
if (true) { // 콜백함수가 성공하면
successCallback(() => { // 더 불러와주세요..
callback(() => ~~~); // *그만...*
});
} else {
failedCallback(() => ~~~);
};
};
} else {
failedCallback();
};
};
};
위 코드를 보시면 콜백함수를 여러번 불러오는 끔찍한 광경을 보실 수 있습니다. 작성하는 사람도 이게 맞나 하면서 작성하고 보는 사람도 그냥 절레절레하게 만드는 코드입니다.
그리고 위 코드는 아까 말했다시피 비동기 함수라서 후속 처리된 결과를 확인하기가 까다롭습니다.
const 요청하기 = function(url) {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
successCallback();
};
};
const res = get(url);
console.log(res); // undefined
};
// 콘솔창에서 get요청한 결과를 볼 수 없습니다.
자바스크립트는 비동기 함수라고 생각되면 시간이 걸릴거라고 판단되기 때문에 태스크 큐에 보관했다가 나머지 코드가 다 실행되고 나서 콜 스택에 푸시됩니다. 그래서 console.log
가 먼저 실행되고 종료된 다음 get
요청이 이루어 집니다. 이러한 이유로 콘솔창에는 undefined
가 출력됩니다.
그래서 get
요청안에서 후속 처리를 해야 결과를 볼 수 있습니다.
get("url1", a => {
successCallback();
});
저렇게 했을 때 후속처리가 끝나면 좋겠지만 안 끝날경우 콜백 함수 안에 콜백 함수를 또 만들어야겠죠? 그러면 위에서 봤던 꼬리물기 코드를 보실 수 있을겁니다.
이렇게 예외 처리를 끝없이 하고 가독성도 떨어지는 문제점을 해결하기 위해 프로미스(Promise)가 나왔습니다.
프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체입니다. new 연산자와 함께 Promise객체를 생성하고 비동기 처리를 수행할 콜백 함수를 resolve, reject인자로 받습니다.
const 요청하기 = function(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));
};
});
};
};
눈치 채셨듯이 resolve는 성공시 비동기 처리 결과를 반환하고 reject는 실패시 처리 결과를 반환합니다.
resolve된 상태를 fulfilled상태라고 하고 reject된 상태는 rejected상태라고 합니다. 둘 다 아닌 상태는 pending상태라고 합니다.
status: pending — resolve(value) → status: fulfilled, result: value
status: pending — reject(error) → status: rejected, result: error
비동기 처리가 수행된 상태는 settled상태라고 합니다.
status → settled
프로미스는 비동기 처리 상태가 되면 후속 처리를 해줘야 합니다. 예를 들어, 프로미스가 fulfilled상태가 되면 프로미스의 처리 결과를 가지고 무언가를 해야 하고, rejected상태가 되면 에러를 가지고 에러 처리를 해야 합니다.
then메서드는 두개의 인수를 가지는데 첫 번째 인수는 비동기 처리가 성공했을 때를 처리하고 두 번째는 실패했을 때 호출되는 콜백 함수 입니다.
new Promise(resolve => resolve('fulfilled'))
.then(a => console.log(a), e => console.error(e));
catch메서드는 프로미스가 rejected상태인 경우만 호출됩니다. 비동기 처리가 실패했을 때 실행됩니다.
new Promise(reject => reject(new Error('rejected')));
.catch(err => console.log(err));
then메서드의 두번째 인자를 사용해도 되지만 코드의 가독성때문에 catch메서드로 에러처리를 하는 것을 권장합니다.
finally메서드는 성공 여부 상관없이 무조건 실행되는 메서드입니다.
new Promise(resolve => resolve("fulfilled"))
.then(a => console.log(a))
.catch(err => console.log(err))
.finally(() => console.log('finally'));
then또는 catch가 실행되고 마지막엔 항상 finally가 실행됩니다.
이미 존재하는 값을 래핑하여 프로미스를 생성하기 위해 사용합니다.
const resolvedPromise = Promise.resolve([1, 2, 3]);
resolvedPromise.then(console.log); // *[1, 2, 3]*
const rejectedPromise = Promise.reject(new Error('Error'));
rejectedPromise.catch(console.log) // *Error*
여러 개의 비동기 처리를 모두 병렬처리할 때 사용됩니다.
const data1 = () => {
new Promise(resolve => setTimeout(() => resolve(1), 2000));
};
const data2 = () => {
new Promise(resolve => setTimeout(() => resolve(2), 3000));
};
const data3 = () => {
new Promise(resolve => setTimeout(() => resolve(3), 1000));
};
Promise.all([data1(), data2(), data3()])
.then(console.log) // *[1, 2, 3] 약 3초*
.catch(console.log)
Promise.all메서드는 모든 프로미스가 모두 fulfilled상태가 되야 종료됩니다. 따라서 가장 늦게 fulfilled되는 프로미스의 처리 시간보다 약간 더 걸립니다. 위 코드를 예로 들면 3초보다 조금 더 시간이 들겠네요.
rejected상태가 되면 가장 먼저 reject된 프로미스를 catch메서드의 인자로 전달합니다.
// 3
가장 먼저 fulfilled되는 프로미스의 처리 결과를 resolve하는 새로운 프로미스를 반환합니다.
Promise.race([
new Promise(resolve => setTimeout(() => resolve(1), 2000)),
new Promise(resolve => setTimeout(() => resolve(2), 3000)),
new Promise(resolve => setTimeout(() => resolve(3), 1000)),
])
.then(console.log) // 3
.catch(console.log)
reject상태는 Promise.all과 같습니다.
이 메서드는 프로미스를 요소로 갖는 배열 등의 이터러블을 인수로 전달받습니다. 그리고 전달받은 프로미스가 모두 settled상태가 되면 fulfilled상태, rejected상태 처리 결과를 모두 배열로 반환합니다.
Promise.allSettled([
new Promise(resolve => setTimeout() => resolve(1), 1000),
new Promise(reject => setTimeout(() => reject(new Error("Error")), 2000))
]).then(console.log);
// [
// {status: "fulfilled", value: 1}
// {status: "rejected", reason: Error: Error at <anonymous>}
// ]
이 기능은 ES11(ECMAScript 2020)에 도입되어 인터넷 익스플로러를 제외한 대부분의 브라우저에서 지원합니다.
애매하거나 틀린 부분이 있으면 피드백 감사하겠습니다.