JavaScript 비동기 처리 시리즈
- [JavaScript] JavaScript 기본 문법 (8) - Javascript의 비동기 처리 (1) - 콜백(Callback)
- [JavaScript] JavaScript 기본 문법 (10) - Javascript의 비동기 처리 (3) - async/await
Promise 기본
Promise 객체의 기본 형태와 resolve, reject 콜백
let promise = new Promise(function (resolve, reject) {
// code
});
Promise 객체는 이런 방식으로 만들 수 있다.
new Promise()
에 인자로 전달되는 함수(function (resolve, reject){ }
)는 executor 라고 부른다.
executor 는 new Promise()
로 Promise 객체가 만들어질 때 자동으로 실행된다.
executor 의 인수 resolve
, reject
는 JavaScript에서 자체 제공하는 콜백(Callback)이다.
executor 내에서 인수로 넘겨준 콜백 resolve
, reject
중 하나를 반드시 호출해줘야 한다.
resolve("value")
- 작업 성공 시 결과값을 나타내는"value"
와 함께 호출reject("error")
- 에러 발생 시 에러 객체"error"
와 함께 호출
정리하자면, executor 는 Promise 객체가 생성될 때 자동으로 실행되며, 그 성공 여부에 따라 resolve 또는 reject를 호출해주면 된다.
Promise 객체의 내부 property - state, result
Promise 객체는 state
와 result
라는 내부 property(프로퍼티)를 갖는다.
State
state는 Promise 객체의 처리 상태에 따라 3가지 상태를 갖는다.
- Pending - 비동기 처리가 완료되지 않은 상태.
- Fulfilled -
resolve()
가 호출되어"value"
를 반환한 상태. - Rejected -
reject()
가 호출되어 처리에 실패하거나"error"
를 반환한 상태.
Result
Result도 마찬가지로 Promise 객체의 state에 따라,
- pending 상태일 때는
undefined
resolve("value")
가 호출되었을 때는"value"
reject("error")
가 호출되었을 때는"error"
의 값을 갖는다.
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
const value = 1;
resolve(value);
}, 1000);
});
/* 1초 후, Promise 객체의 state - fulfilled, result - 1 */
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
reject(new Error("Error Msg"));
}, 1000);
});
/* 1초 후, Promise 객체의 state - rejected, result - Error : Error Msg */
then, catch, finally
Promise 객체를 생성하고 executor가 result
로 value
또는 error
를 뱉어낸다.
그렇다면, 그놈들을 이제 받아서 처리할 아이들이 있어야 할 것이 아닌가?
그게 바로 .then()
, .catch()
, .finally()
다.
이 아이들은, 함수를 인자로 받는다!
then
Usage
promise.then(
function(result) { /* 결과(result)를 다룹니다 */ },
function(error) { /* 에러(error)를 다룹니다 */ }
);
then은 두 가지 인자를 받는다.
첫 번째 인자는 Promise 객체가 resolve
되었을 때 실행되는 함수,
두 번째 인자는 Promise 객체가 reject
되었을 때 실행되는 함수.
물론 당연히,
첫 번째 함수의 인자 (위 Usage에서
function(result)
의result
) 는 Promise 객체에서resolve()
로 반환한 value이고,두 번째 함수의 인자 (위 Usage에서
function(error)
의error
) 는 Promise 객체에서reject()
로 반환한 error이다.
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve("Promise resolved!");
}, 1000);
});
promise.then(
(value) => { console.log(value); },
(error) => { console.log(error); }
);
// output : (Promise resolved!)
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
reject(new Error("Promise rejected!"));
}, 1000);
});
promise.then(
(value) => { console.log(value); },
(error) => { console.log(error); }
);
// output : (Error: Promise rejected!)
첫 번째 코드는 resolve되었을 때, 두 번째 코드는 reject되었을 때의 예시이다.
이런 느낌의 흐름으로 resolve에서 뱉어낸 result가 흘러간다. 물론 error의 경우도 똑같음.
이렇게 then 하나로 Promise 객체가 resolve, reject되었을 때 모두 처리할 수 있지만, 이를 추천하지 않는다.
이에 대해서는 아래에서 다시 서술한다.
catch
Usage
promise.catch(
function(error) { /* 에러(error)를 다룹니다 */ }
);
catch는 then과 다르게, error가 발생한 경우(Promise 객체가 reject되었을 때 + etc..) 에 실행될 함수만을 인자로 갖는다.
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
reject(new Error("An Error Occurred!"));
}, 1000);
});
promise.catch((error) => {
console.log(error);
});
/* output :
Error: An Error Occurred! */
기본적으로 then에서 두 번째 인자만 존재한다고 보면 거의 비슷하게 작동한다.
Promise의 Error Handling은 가급적 catch로
위에서 보았듯이, .then()
을 이용해 성공과 실패 모두 처리할 수 있다.
그러나 가급적이면 .then()
으로 성공 시(resolve) 처리를, .catch()
로 실패 시(reject) 에러 처리를 하는 것을 추천한다.
그 이유는 아래에서 볼 수 있는데,
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve("Promise resolved!");
}, 1000);
});
promise.then(
(value) => {
throw new Error("Error Occurred!");
},
(error) => {
console.log(error);
}
);
와 같이 하나의 then으로 resolve와 reject를 모두 처리하게 되면, then 내에서 발생하는 error에 대해서는 처리하지 못하는 문제가 발생한다.
이를 catch를 이용한 코드로 바꿔준다면, 깔끔하게 처리가 가능하다.
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve("Promise resolved!");
}, 1000);
});
promise
.then((value) => {
throw new Error("Error Occurred!");
})
.catch((error) => {
console.log(error);
});
/* output :
Error: Error Occurred! */
finally
try - catch
문법과 유사하게, Promise에도 finally
가 존재한다.
물론 처리되는 과정도 유사하게, Promise가 resolve
되든 reject
되든 상관없이 Promise가 처리되기만 하면 finally
를 수행한다.
Usage
new Promise(function (resolve, reject) {
resolve("Promise Resolved!");
})
.finally(() => {
console.log("Promise Finish!");
})
.then((value) => {
console.log(value);
});
/* output :
Promise Finish!
Promise Resolved!
*/
new Promise(function (resolve, reject) {
setTimeout(() => {
reject(new Error("An Error Occurred!"));
}, 1000);
})
.finally(() => {
console.log("Promise Finish!");
})
.catch((error) => {
console.log(error);
});
/* output :
Promise Finish!
Error: An Error Occurred!
*/
Promise Chaining(Promise 체이닝)
Promise Chaining
.then()
에서는 Promise 객체 가 return된다.
따라서, 반환된 Promise 객체에 다시 .then()
을 붙여 처리할 수 있다. 이를 Promise Chaining 이라 한다.
.then()
에서 Promise 객체가 아닌 값을 return할 때엔, 그 값을result
값으로 가진 Promise 객체가 return된다.
new Promise((resolve, reject) => {
const value = 1;
resolve(value);
})
// Promise 객체를 return하여 다음 then으로 넘겨줌!
// return하는 Promise 객체의 result값은 value*2인 "2"
.then((value) => {
return new Promise((resolve, reject) => {
console.log(value);
resolve(value * 2);
});
})
// Promise 객체가 아닌 다른 값을 return할 때는
// result값이 value*2 ("4")인 Promise 객체를 return.
.then((value) => {
console.log(value);
return value * 2;
})
.then((value) => {
console.log(value);
});
/* output :
1
2
4
*/
line 8 에서처럼 then이 Promise 객체를 직접 return할 수도 있고,
line 17 에서처럼 then이 특정 값을 return하면 그 값이 Promise 객체의 result값이 되어 return된다!
Error Handling
Promise가 reject
되면 제일 가까운 reject handler(.catch()
와 같은..)로 넘어간다.
따라서 Promise chaining을 이용하면, 에러를 쉽게 처리할 수 있다.
new Promise((resolve, reject) => {
const value = 1;
resolve(value);
})
.then((value) => {
console.log(value);
return value * 2;
})
.then((value) => {
console.log(value);
return new Error("Error in Promise Chain");
})
.then((value) => {
console.log(value);
return value * 2;
})
.catch((error) => {
console.log(error);
});
/* output :
1
2
Error: Error in Promise Chain
*/
line 11 에서 throw한 Error를 line 17에서 catch하여 처리함. line 13의 3번째 then은 무시된다.
이런 식으로 진행된다!
물론 한 번의 catch 이후 다시 error를 catch하는 것도 가능하다!
new Promise((resolve, reject) => {
const value = 1;
resolve(value);
})
.then((value) => {
console.log(value);
THIS_IS_NOT_A_FUNCTION();
// 꼭 Error 객체를 throw, return하는 것이 아니더라도 모든 종류의 error를 catch한다.
// line 15에서 catch
})
.then((value) => { // 무시되는 then
console.log(value);
return value * 2;
})
.catch((error) => {
console.log(error);
return new Error("2nd Error");
// line 23에서 catch
})
.then((value) => { // 무시되는 then
console.log(value);
})
.catch((error) => {
console.log(error);
});
/* output :
1
ReferenceError: THIS_IS_NOT_A_FUNCTION is not defined
Error: 2nd Error
*/
line 7 처럼 Error 객체를 throw / return하지 않더라도 모든 종류의 error를 handling한다.
Promise API
Promise Class에는 5가지 static method가 존재한다.
Promise.all
Usage
let promise = Promise.all([...promises...]);
Promise의 배열을 받아 새로운 Promise를 반환한다.
배열 안의 모든 Promise가 처리되면, 그 Promise들의 result를 담은 배열을 만들어 새로운 Promise의 result값으로 만든다.
다만, Promise 배열 중 하나라도 reject되면, Promise.all
이 반환하는 새로운 Promise는 reject된다.
let promise = Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
}),
]);
promise.then((value) => {
console.log(value);
});
// output : [ 1, 2, 3 ]
모든 Promise가 resolve되었을 때.
result들의 배열이 새로운 Promise의 result로 반환된다.
let promise = Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
}),
new Promise((resolve, reject) => { // 얘만 reject!
setTimeout(() => {
reject(new Error("Error in 2nd Promise"));
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
}),
]);
promise
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
});
// output :
// Error: Error in 2nd Promise
두 번째 Promise만 reject되었을 때.
하나라도 reject되면 새롭게 만들어지는 Promise는 reject처리된다.
Promise.allSettled
Promise.all
은 하나라도 reject되면 전체가 reject되지만,
Promise.allSettled
는 모든 Promise를 우선 처리하고, 모든 Promise 객체의 배열을 반환한다.
- 응답이 성공할 경우 –
{status:"fulfilled", value:result}
- 에러가 발생한 경우 –
{status:"rejected", reason:error}
let promise = Promise.allSettled([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Error in 2nd Promise"));
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
}),
]);
promise
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
});
/* output :
[
{ status: 'fulfilled', value: 1 },
{
status: 'rejected',
reason: Error: Error in 2nd Promise
},
{ status: 'fulfilled', value: 3 }
]
*/
Promise.race
Promise.race
는 Promise들 중 가장 먼저 처리되는 Promise의 result
또는 error
를 result
/ error
로 가진다.
let promise = Promise.race([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Error in 2nd Promise"));
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 500);
}),
]);
promise
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
});
// output : 3
3번째 Promise가 0.5초 이후 가장 빨리 결과를 반환하므로, 3번째 Promise의 result인 "3" 이 Promise.race에서 반환하는 Promise 객체의 result가 된다.
Promise.resolve / Promise.reject
let promise = Promise.resolve(1);
promise.then((value) => {
console.log(value);
});
// output : 1
let promise = Promise.reject(new Error());
promise.catch((error) => {
console.log(error);
});
// output : Error
Promise.resolve는 resolve된 Promise 객체를,
Promise.reject는 reject된 Promise 객체를 반환한다.
다음 글에서 다룰 async/await 등장 이후 쓸모가 없어졌기 때문에, 간략하게 넘어가도록 하겠다.
<= [JavaScript] JavaScript 기본 문법 (8) - Javascript의 비동기 처리 (1) - 콜백(Callback)
=> [JavaScript] JavaScript 기본 문법 (10) - Javascript의 비동기 처리 (3) - async/await