비동기를 간편하게 처리할 수 있도록 도와주는 자바스크립트에 내장되어 있는 object
이다.
콜백 함수를 사용하면 가독성이 나쁘고 비동기 처리 중 발생한 에러의 처리가 곤라한 일이 발생한다.
es6에서 콜백의 문제점을 보완하고자 도입한 것이 promise
이다. 프로미스는 비동기 이면서 비동기 처리 시점을 명확하게 표현할 수 있다.
let promise = new Promise(function (resove, reject) {
// executor (제작 코드 , '가수')
})
new Promise
에 전달되는 함수,new Promise
가 만들어질 때 자동으로 실행되고 결과를 최종적으로 만들어내는 제작 코드를 포함한다.executor
는 자동으로 실행되는데 원하는 일을 처리되면 executor는 처리 성공 여부에 따라 resolve , reject 를 호출한다.
주의점 -
executors
는 자동으로 처리되기 때문에 자동으로 처리하면 안되는 일의 코드를 넣어버리면 불필요한 통신이 일어날 수 있다.
new Promise
생성자가 반환하는 promise
객체는 다음과 같은 내부 프로퍼티를 갖는다.
state — 처음엔 "pending"(보류)
이었다 resolve
가 호출되면"fulfilled"
,
reject
가 호출되면 "rejected"
로 변한다.
result — 처음엔 undefined
이었다 resolve(value)
가 호출되면 value
로, reject(error)
가 호출되면 error
로 변한다.
생성된 Promise 객체를 사용하기 위해서는 .then
, .catch
, .finally
메소드를 사용하여 연결시켜야한다. 소비자,소비함수 라고도 한다.
promise.then(
function(result) { /* 결과(result)를 다룹니다 */ },
function(error) { /* 에러(error)를 다룹니다 */ }
);
.then
의 첫 번째 인수 : 프라미스가 이행되었을 때 실행되는 함수이고, 여기서 실행 결과를 받는다.
.then
의 두 번째 인수 : 프라미스가 거부되었을 때 실행되는 함수이고, 여기서 에러를 받는다.
에러가 발생한 경우만 다루고 싶다면 .then(null, errorHandlingFunction)
같이 null
을 첫 번째 인수로 전달하면 됩니다. .catch(errorHandlingFunction)
를 써도 된다.
프라미스가 처리되면 항상 어떤 함수를 실행하고 싶을때 finally를 사용한다.
쓸모가 없어진 로딩 인디케이터(loading indicator)를 멈추는 경우같이, 결과가 어떻든 마무리가 필요하면 finally가 유용하다.
사용법
new Promise((resolve, reject) => {
/* 시간이 걸리는 어떤 일을 수행하고, 그 후 resolve, reject를 호출함 */
})
// 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨
.finally(() => 로딩 인디케이터 중지)
.then(result => result와 err 보여줌 => error 보여줌)
.finally(f)
!==.then(f, f)
둘의 차이점은 다음과 같다.
1. finally 핸들러에는 인수가 없다. 프라미스가 이행되었는지 거부되었는지 알 필요가 없기 때문이다.
2. finally 핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달한다. 따라서 finally는 결과를 처리하기위해 만든 것이 아니다. 결과는 finally를 통과해서 전달된다.
3. 함수를 중복해서 쓸 필요가 없기 때문에 .then(f, f) 보다 문법 측면에서 편리하다.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
promise chaining
이 가능한 이유는 .then
을 호출하면 promise 가 반환되고, 반환된 promise에도 역시 .then
을 호출 할 수 있다.
핸들러가 값을 반환할 때엔 이 값이 프라미스의 result가 된다. 따라서 다음 .then은 이 값을 이용해 호출된다.
.then(handler)
에 사용된 핸들러가 프라미스를 생성하거나 반환하는 경우도 있다.
이 경우 이어지는 핸들러는 프라미스(👉)가 처리될 때까지 기다리다가 처리가 완료되면 그 결과를 받는다.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
👉 return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
가장 쉬운 에러 처리 방법은 체인 끝에 .catch를 붙이는 것이다.
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise((resolve, reject) => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
.catch(error => alert(error.message));
정상적인 경우라면 .catch는 절대 트리거 되지 않는다. 그런데 네트워크 문제, 잘못된 형식의 JSON 등으로 인해 위쪽 프라미스 중 하나라도 거부되면 .catch에서 에러를 잡게 된다.
프라미스 executor와 프라미스 핸들러 코드 주위엔 '보이지 않는 try...catch'가 있다. 예외가 발생하면 암시적 try...catch에서 예외를 잡고 이를 reject처럼 다룬다.
new Promise((resolve, reject) => {
throw new Error("에러 발생!");
}).catch(alert); // Error: 에러 발생!
위는 아래와 동일하게 동작한다.
new Promise((resolve, reject) => {
reject(new Error("에러 발생!"));
}).catch(alert); // Error: 에러 발생!
핸들러 안에서도 throw
를 사용해 에러를 던지면, 이 자체가 거부된 프라미스를 의미하게 된다. 따라서 제어 흐름이 가장 가까운 에러 핸들러로 넘어간다.
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
throw new Error("에러 발생!"); // 프라미스가 거부됨
}).catch(alert); // Error: 에러 발생!
꼭 throw
문으로 만든 에러가 아니라도 모든 종류의 에러를 처리할 수 있다. 핸들러 위쪽에서 비정상적으로 발생한 에러 또한 잡는다.
체인 마지막의 .catch
는 try..catch
와 유사한 역할을 합니다. .then
핸들러를 원하는 만큼 사용하다 마지막에 .catch
하나만 붙이면 .then
핸들러에서 발생한 모든 에러를 처리할 수 있습니다.
// 실행 순서: catch -> then
new Promise((resolve, reject) => {
throw new Error("에러 발생!");
}).catch(function(error) {
alert("에러가 잘 처리되었습니다. 정상적으로 실행이 이어집니다.");
}).then(() => alert("다음 핸들러가 실행됩니다."));
에러를 처리하지 못하는 상황, 즉 체인 끝에 .catch를 추가하지 못하는 경우엔 어떻게 될까? 에러가 발생하면 프라미스는 거부상태가 된다.
new Promise(function() {
noSuchFunction(); // 에러 (존재하지 않는 함수)
})
.then(() => {
// 성공상태의 프라미스를 처리하는 핸들러. 한 개 혹은 여러 개가 있을 수 있음
}); // 끝에 .catch가 없음!
자바스크립트 엔진은 프라미스 거부를 추적하다가 위와 같은 상황에는 전역 에러를 생성한다. 브라우저 환경에서는 이런 에러를 unhandledrejection
이벤트로 잡을 수 있다.
window.addEventListener('unhandledrejection', function(event) {
// 이벤트엔 두 개의 특별 프로퍼티가 있습니다.
alert(event.promise); // [object Promise] - 에러를 생성하는 프라미스
alert(event.reason); // Error: 에러 발생! - 처리하지 못한 에러 객체
});
new Promise(function() {
throw new Error("에러 발생!");
}); // 에러 처리 핸들러, catch가 없음
브라우저 환경에서 에러가 발생했는데 .catch가 없으면 unhandledrejection 핸들러가 트리거 된다.
unhandledrejection
핸들러는 에러 정보가 담긴 event 객체를 받기 때문에 이 핸들러 안에서 원하는 작업을 할 수 있다.
모든 프라미스가 이행될 때까지 기다렸다가 그 결과값을 담은 배열을 반환한다. 주어진 프라미스 중 하나라도 실패하면 Promise.all은 거부되고 나머지 프라미스의 결과는 무시된다.
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
Promise.all
은 요소 전체가 프라미스인 배열을 받고 새로운 프라미스를 반환한다.
배열 안 프라미스가 모두 처리되면 새로운 프라미스가 이행되는데, 배열 안 프라미스의 결괏값을 담은 배열이 새로운 프라미스의 result가 된다.
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 프라미스 전체가 처리되면 3초뒤에 1, 2, 3이 반환됩니다. 각 프라미스는 배열을 구성하는 요소가 됩니다.
위 코드에서 result는 배열 [1, 2, 3] 이다.
Promise.all
의 첫 번째 프라미스는 가장 늦게 이행되지만 처리 결과는 배열의 첫 번째 요소에 저장된다.
🚨유의점
Promise.all
에 전달되는 프라미스 중 하나라도 거부되면, Promise.all
이 반환하는 프라미스는 에러와 함께 바로 거부된다.
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("에러 발생!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: 에러 발생!