Promise
는 자바스크립트에서 비동기 작업을 처리하는 객체입니다.Promise
는 어떤 작업이 완료되거나 실패했을 때 실행할 콜백 함수를 정의할 수 있는 객체입니다.Promise
는 "약속"입니다. 무언가를 약속하고 그 약속을 지키면 뭔가를 실행하거나, 지키지 못하면 다른 일을 할 수 있게 도와주는 것입니다.pending
)되면, 요리사는 주문 성공(resolve
) 또는 주문 실패(reject
)라는 약속을 합니다.resolve
)하면, 주문한 음식을 받아서 먹을 수 있게 됩니다.reject
)하면, 요리사가 다른 음식을 만들어 알려주면서 어떤 문제가 있었는지 설명해줄 수 있습니다.function addNumbers(num1, num2, callback) {
setTimeout(() => {
const result = num1 + num2;
callback(result);
}, 1000);
}
addNumbers(2, 3, (n1) => {
addNumbers(n1, 5, (n2) => {
addNumbers(n2, 5, (n3) => {
addNumbers(n3, 5, (n4) => {
addNumbers(n4, 5, (n5) => {
console.log("The final number is: " + n5);
});
});
});
});
});
콜백 함수가 중첩되면서 코드의 깊이가 깊어지면 코드의 가독성을 떨어뜨리고 코드의 흐름을 파악하기 어렵습니다. 또한 콜백 함수마다 에러처리를 따로 해줘야하며 에러가 발생한 위치를 찾기 어렵습니다.
function addNumbers(num1, num2) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const result = num1 + num2;
resolve(result);
}, 1000);
});
}
addNumbers(2, 3)
.then((n1) => addNumbers(5, n1))
.then((n2) => addNumbers(5, n2))
.then((n3) => addNumbers(5, n3))
.then((n4) => addNumbers(5, n4))
.then((n5) => console.log("The final number is: " + n5));
비동기적으로 처리해야 하는 일이 많아질수록, 코드의 깊이가 계속 깊어지는 현상이 있는데, Promise
를 사용하면 코드의 깊이가 깊어지는 현상을 방지할 수 있습니다.
const myPromise = new Promise((resolve, reject) => {
// executor (비동기 작업 수행)
const data = fetch('서버로부터 요청할 URL');
if(data)
resolve(data); // 만일 요청이 성공하여 데이터가 있다면
else
reject("Error"); // 만일 요청이 실패하여 데이터가 없다면
})
executor
는 새로운 Promise
객체가 생성될 때 자동으로 실행되는 함수입니다. new
키워드와 Promise
생성자 함수를 사용하여 생성할 수 있습니다. executor
함수는 resolve
와 reject
라는 두 개의 매개변수를 가지며, 비동기 작업을 수행한 후 작업이 성공한 경우 resolve
함수를 호출하고, 작업이 실패한 경우 reject
함수를 호출합니다.
function fetchData() {
return new Promise((resolve, reject) => {
// 비동기 작업 수행
// 데이터를 성공적으로 가져오면 resolve 호출
// 에러가 발생하면 reject 호출
setTimeout(() => {
const data = "This is the fetched data";
if (data) {
resolve(data); // 데이터를 성공적으로 가져옴
} else {
reject("Failed to fetch data"); // 데이터 가져오기 실패
}
}, 2000);
});
}
fetchData() // fetchData 함수를 호출하고 프로미스를 처리합니다.
.then((data) => { // 성공적으로 수행했을 때 실행될 코드
console.log("Data:", data); // resolve(data)의 data값이 출력된다
})
.catch((error) => { // 실패했을 때 실행될 코드
console.error("Error:", error); // reject("Failed to fetch data")가 출력된다
});
위 코드는 Promise
를 반환합니다. 비동기 작업이 완료된 이후 작업 결과에 따라 then()
과 catch()
를 사용하여 성공과 실패에 대한 후속 처리를 진행할 수 있습니다. 주어진 코드에서 data
가 존재하는 경우, resolve(data)
를 호출하고, 그 후 .then()
을 연결하여 콜백 함수에서 성공에 대한 추가 처리를 수행합니다. resolve()
함수의 매개변수 값은 then
메서드의 콜백 함수 인자로 전달되어, 프로미스 객체 내부에서 다룬 값을 사용할 수 있게 됩니다. 이 결과로 "This is the fetched data" 가 출력됩니다. 만약 처리가 실패하여 reject()
를 호출하게 되면, .catch()
로 이어져 catch
메서드의 콜백 함수에서 실패에 대한 추가 처리를 진행하며 "Failed to fetch data" 를 출력합니다.
프로미스 객체를 변수로 할당하는 방식은 사용할 수 있지만, 보통의 경우 함수로 감싸서 사용하는 것이 더 일반적입니다.
const Promise1 = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
console.log("This is the myPromise1");
}, 1000);
});
};
const Promise2 = new Promise((resolve) => {
setTimeout(() => {
resolve();
console.log("This is the myPromise2");
}, 1000);
});
위 코드에서 Promise1
은 프로미스 객체를 반환하도록 구현했으며, Promise2
는 프로미스 객체를 할당했습니다. executor
는 새로운 Promise
객체가 생성될 때 자동으로 실행되는 함수입니다. 이 말은 Promise2
는 즉시 실행되지만, Promise1
은 호출하기 전까지 실행되지 않는다는 것을 의미합니다. 따라서 함수로 만들고 그 함수를 호출하면 프로미스 객체를 반환하여 생성된 프로미스 객체를 함수의 반환값으로 사용할 수 있습니다. 원하는 시점에 호출하거나 재사용하고 싶은 경우, Promise1
과 같이 프로미스 객체를 반환하는 함수를 만들어야 합니다.
"동적으로 비동기 작업을 수행한다" 이 말은 함수
fetchData
에 동적으로 전달된URL
에 대한 비동기 데이터를 가져오는 작업을 수행한다는 내용입니다.
const fetchData = (url) => {
return new Promise((resolve, reject) => {
// 동적으로 전달된 url을 이용하여 비동기 작업을 수행
setTimeout(() => {
const data = "Data fetched successfully from " + url;
if (data) {
resolve(data);
} else {
reject(new Error("Failed to fetch data from " + url));
}
}, 1000);
});
};
const processData = (data) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const processedData = "Processed data: " + data;
if (processedData) {
resolve(processedData);
} else {
reject(new Error("Failed to process data"));
}
}, 1000);
});
};
fetchData(`https://jsonplaceholder.typicode.com/posts/1`)
.then((data) => processData(data))
.then((processedData) => {
console.log("Final result:", processedData);
})
.catch((error) => {
console.error("Error:", error);
});
위 코드는 fetchData
와 processData
라는 두 개의 함수로 구성된 프로미스입니다. fetchData
함수는 주어진 URL
에서 데이터를 가져오는 프로미스를 반환하고, processData
함수는 데이터를 가공하는 프로미스를 반환합니다. 이렇게 프로미스를 사용하면 fetchData
와 processData
함수를 재사용할 수 있습니다. 예를 들어 다른 API 엔드포인트에서 데이터를 가져와 처리해야 하는 경우에도 같은 로직을 사용할 수 있습니다. 이런 재사용성은 코드의 유지 보수성을 높이고, 중복을 최소화하며 개발 생산성을 향상시킬 수 있습니다.
API 엔드포인트
API 엔드포인트는 웹 서비스 또는 웹 애플리케이션에서 외부에 제공되는 특정 URI 또는 URL을 의미합니다. 클라이언트가 서버와 통신할 때 해당 리소스에 접근하는 데 사용되는 URL 주소가 API 엔드포인트입니다."다른 API 엔드포인트에서 데이터를 가져와 처리"라는 문장은 다른 서버 또는 다른 리소스에서 데이터를 가져와서 그 데이터를 처리하는 것을 의미합니다. 위 코드에서는
fetchData
함수가 이러한 엔드포인트를 사용하여 데이터를 가져오고, 이후processData
함수에서 해당 데이터를 처리합니다.
Promise
는 처리과정을 의미하는 3가지 상태(state)를 가지고 있습니다. Promise
를 생성하고 종료될 때까지의 3가지 상태를 갖습니다. new Promise
생성자가 반환하는 Promise
객체는 다음과 같은 내부 프로퍼티를 갖습니다.
Pending
(대기) : 프로미스가 아직 처리되지 않은 상태입니다.Fulfilled
(이행) : 프로미스가 성공적으로 처리되어 결과 값을 반환한 상태입니다.Rejected
(거부) : 프로미스가 처리 중에 오류가 발생하여 처리를 실패한 상태입니다.Pending
상태는 프로미스 객체가 생성되고, 비동기 작업이 아직 완료되지 않은 초기 상태를 나타냅니다. 프로미스 객체를 생성한 후 콘솔로 확인해보면 프로미스 객체의 상태가 Pending
으로 출력됩니다.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("처리 완료");
}, 5000);
});
console.log(myPromise); // "pending"
위에서 작성한 코드를 실행한 후 5초 뒤에 콘솔로 다시 확인하면 Fulfilled
상태로 변하게 됩니다. 5초가 지나면 resolve("처리 완료")
가 실행되어 프로미스의 성공을 알리는 개체를 호출하고, 비동기 로직이 성공적으로 완료되었음을 나타내는 상태를 보여줍니다.
그리고 Fulfilled
상태로 변한 프로미스는 .then()
을 연결하여 성공에 대한 추가 처리를 수행할 수 있습니다.
myPromise.then((data) => {
console.log("프로미스 처리 완료!!!");
});
비동기 작업이 실패하거나 에러가 발생한 상태입니다. reject("처리 실패")
를 호출하면 프로미스 객체가 Rejected
상태가 됩니다. 5초뒤 콘솔로 확인해보면 Rejected
가 출력되는걸 확인할 수 있습니다.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("처리 실패");
}, 5000);
});
console.log(myPromise);
Rejected
상태로 변한 프로미스는 .catch()
을 연결하여 실패에 대한 추가 처리를 진행할 수 있습니다.
myPromise.catch((error) => {
console.log(error);
console.log("프로미스 처리 실패!!!");
});
프로미스의 체이닝은 여러 개의 Promise를 연결하여 비동기 작업을 순차적으로 처리하는 방식입니다. 아래 코드는 순차적으로 다섯 번의 비동기적인 숫자 덧셈 작업을 수행합니다. 각 작업은 이전 작업의 결과값을 기반으로 차례로 실행되며, 마지막에 최종 결과값이 콘솔에 출력됩니다.
function addNumbers(num1, num2) {
return new Promise((resolve) => {
setTimeout(() => {
const result = num1 + num2;
resolve(result);
}, 1000);
});
}
addNumbers(2, 3)
.then((n1) => addNumbers(5, n1))
.then((n2) => addNumbers(5, n2))
.then((n3) => addNumbers(5, n3))
.then((n4) => addNumbers(5, n4))
.then((n5) => console.log("The final number is: " + n5));
프로미스에는 여러 정적(static)메소드가 있습니다. 이러한 정적 메소드들은 주로 프로미스의 생성과 관련된 작업을 수행하거나, 여러 프로미스를 조작하고 처리하는 데 사용됩니다.
Promise.all()
은 여러 개의 프로미스를 동시에 실행하고, 모든 프로미스가 완료될 때까지 기다렸다가 그 결과를 배열로 반환하는 메소드입니다.
const promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise 1 resolved');
}, 2000);
});
const promise2 = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise 2 resolved');
}, 3000);
});
const promise3 = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise 3 resolved');
}, 1500);
});
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log(results);
})
.catch((error) => {
console.error(error);
});
위 코드에서 Promise.all()
은 [promise1, promise2, promise3]
배열의 프로미스들을 동시에 실행합니다. 모든 프로미스가 완료되면, .then()
메소드가 실행되고, 각 프로미스의 결과가 results
배열로 전달됩니다. 만약 하나라도 프로미스가 거부된다면 .catch()
블록이 실행됩니다.
Promise.race()
메소드는 입력된 프로미스들 중에서 가장먼저 fulfilled(이행)되거나 rejected(거부)된 프로미스를 기반으로 새로운 프로미스를 반환합니다.
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 1');
}, 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 2');
}, 1000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Promise 3');
}, 3000);
});
const promises = [promise1, promise2, promise3];
Promise.race(promises)
.then(result => {
console.log(result); // 'Promise 2'
})
.catch(error => {
console.error(error);
});
Promise.race()
는 fulfilled(이행) 또는 rejected(실패) 여부와 관계없이 가장 먼저 처리가 끝난 Promise2
를 콘솔에 출력합니다.
Promise.all()
은 주어진 모든 프로미스가 모두 완료되어야만 결과를 도출하는 반면, Promise.any()
는 주어진 모든 프로미스 중 하나라도 완료(fulfilled)되면 즉시 반환하는 정적 메서드입니다.
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Promise 1');
}, 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Promise 2');
}, 1000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 3');
}, 3000);
});
const promises = [promise1, promise2, promise3];
Promise.any(promises)
.then(result => {
console.log(result); // 'Promise 3'
})
.catch(errors => {
console.error(errors); // ['Promise 1', 'Promise 2']
});
예제 코드를 보면 Promise3
이 출력되는 것을 확인할 수 있습니다. Promise.any()
는 여러 프로미스 중에서 가장 먼저 이행(fulfilled)된 프로미스의 결과를 반환하기 때문에 거부(rejected) 상태인 Promise1
과 Promise2
는 무시됩니다.
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Promise 1');
}, 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Promise 2');
}, 1000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 3');
}, 3000);
});
const promises = [promise1, promise2, promise4];
Promise.any(promises)
.then(result => {
console.log(result);
})
.catch(errors => {
console.error(errors); // AggregateError: All promises were rejected
console.error(errors.errors); // ['Promise 1', 'Promise 2', 'promise4 failed']
});
Promise.any()
를 사용하여 여러 프로미스를 처리하고, 결과에서 거부(rejected)된 프로미스 에러들을 AggregateError
로 묶어서 처리합니다.
AggregateError
AggregateError는 여러 에러를 하나의 에러로 묶어주는 객체로, ES2021(ECMAScript2021)에서 도입된 새로운 에러 타입 중 하나입니다. AggregateError는 여러 에러가 발생한 상황에서 모든 에러를 캡슐화 하여 하나의 AggregateError로 처리할 수 있으며, 여러 에러를 한 번에 사용자에게 전달할 때 유용합니다. 이 배열은errors
라는 속성을 통해 접근할 수 있습니다.
Promise.resolve()
는 주어진 값을 가지는 이행(resolve) 상태의 프로미스를 생성하는 메소드입니다. Promise.resolve()
는 두 가지 경우를 다룰 수 있습니다.
const value = 42;
const promise = Promise.resolve(value);
promise.then((result) => {
console.log(result); // 42
});
const existingPromise = new Promise((resolve) => resolve('Hello, Promise!'));
const resolvedPromise = Promise.resolve(existingPromise);
resolvedPromise.then((result) => {
console.log(result); // 'Hello, Promise!'
});
이러한 특징은 특히 함수에서 비동기 작업의 결과를 반환하거나 이미 완료된 작업을 프로미스로 감싸는 등의 상황에서 사용됩니다.
Promise.reject(reason)
는 주어진 이유(reason
)로 거부된(rejected) 프로미스를 반환합니다.
const errorReason = new Error('Something went wrong');
const rejectedPromise = Promise.reject(errorReason);
rejectedPromise.catch((error) => {
console.error('Error:', error.message); // 'Something went wrong'
});
Promise.reject(reason)
는 거부된 상태의 프로미스를 생성하며, catch
메소드를 사용하여 거부된 이유를 처리합니다. 여기서 reason
은 거부된 프로미스의 이유를 설명하는 값입니다. 또한, reason
은 선택적인 매개변수이며, 생략될 경우 기본값은 undefined
입니다.
const rejectedPromise = Promise.reject();
rejectedPromise.catch((reason) => {
console.error('Reason:', reason); // undefined
});