Promise
- 비동기 작업의 완료 또는 실패를 나타내는 객체
- 주로 서버 요청이나 타이머와 같은 비동기 작업에서 사용
- 콜백 함수로 인한 콜백 지옥 문제를 해결할 수 있음
promise 상태
- 대기 : 프로미스가 아직 완료되지 않은 초기 상태
- 이행 : 비동기 작업이 성공적으로 완료되어 결과 값을 반환한 상태
- 거부 : 비동기 작업이 실패하고 오류가 발생한 상태
콜백 지옥...ex)
위와 같은 현상을 흔히 콜백 지옥이라 한다...
// 사용자 데이터를 가져오는 함수
function getUserData(userId, callback) {
setTimeout(() => {
console.log("사용자 데이터를 가져왔습니다.");
callback({ userId: userId, name: "John Doe" });
}, 1000);
}
// 사용자의 주문 목록을 가져오는 함수
function getUserOrders(userData, callback) {
setTimeout(() => {
console.log(`"${userData.name}"의 주문 목록을 가져왔습니다.`);
callback([{ orderId: 1 }, { orderId: 2 }, { orderId: 3 }]);
}, 1000);
}
// 주문의 세부 정보를 가져오는 함수
function getOrderDetails(order, callback) {
setTimeout(() => {
console.log(`주문 ${order.orderId}의 세부 정보를 가져왔습니다.`);
callback({ orderId: order.orderId, details: "주문 세부 정보" });
}, 1000);
}
// 알림을 보내는 함수
function sendNotification(orderDetails, callback) {
setTimeout(() => {
console.log(`주문 ${orderDetails.orderId}에 대한 알림을 보냈습니다.`);
callback();
}, 1000);
}
// 콜백 지옥 예제: 사용자 데이터를 가져오고, 주문 목록을 조회하고, 세부 정보를 가져와 알림을 보냄
getUserData(123, (userData) => {
getUserOrders(userData, (orders) => {
orders.forEach((order) => {
getOrderDetails(order, (orderDetails) => {
sendNotification(orderDetails, () => {
console.log(`주문 ${orderDetails.orderId}에 대한 작업 완료`);
});
});
});
});
});
위 처럼 가독성이 떨어지는 문제를 해결하기 위해서 Promise를 사용한다
원시값과 객체값의 비교
- 원시값 복사:
- 원시값은 값에 의한 복사로, display 함수 안에서 num의 값을 10으로 바꿔도 외부의 value는 변경되지 않습니다.
출력:
// 원시값을 이용해 값을 복사해서 넣어줬다
function display(num) {
num = 10; // 함수 내부에서 num의 값을 10으로 변경
console.log(num); // 10 출력 (함수 내부에서만 변경됨)
}
const value = 5; // 글로벌 스코프에 value라는 원시값 변수 선언
display(value); // display 함수 호출, 값 복사에 의한 호출
console.log(value); // 5 출력 (원본 값은 변하지 않음)
console.log(num); // 10
console.log(value); // 5
- 객체값 복사:
- 객체는 참조에 의한 복사로, 함수 displayObj에서 obj.age를 수정하면 외부의 Rucy 객체도 변경됩니다.
출력:
// 객체값 이용해 원본의 값을 변경해서 넣어줬다
function displayObj(obj) {
obj.age = 15; // 함수 내부에서 객체의 age 속성을 15로 변경
console.log(obj); // {name: '루시', age: 15} 출력 (참조에 의한 복사이므로 원본도 변경됨)
}
const Rucy = {name: '루시', age: 10}; // Rucy라는 객체를 생성
displayObj(Rucy); // displayObj 함수 호출, 객체의 참조값 전달
console.log(Rucy); // {name: '루시', age: 15} 출력 (함수에서 변경된 원본 객체)
console.log(obj); // {name: '루시', age: 15}
console.log(Rucy); // {name: '루시', age: 15}
- 객체의 불변성 유지:
- changeAge 함수는 새로운 객체를 반환하여 Rucy 객체의 원본을 유지하면서 age 속성을 변경합니다.
출력:
function changeAge(obj) {
return { ...obj, age: 7 }; // 새로운 객체를 반환하며, age를 7로 설정 (원본은 유지됨)
}
const PPomi = changeAge(Rucy); // Rucy 객체를 changeAge에 전달하여 age가 7인 새 객체 생성
console.log(Rucy); // {name: '루시', age: 15} 원본 객체는 변하지 않음
console.log(PPomi); // {name: '루시', age: 7} 새롭게 생성된 객체 출력
console.log(Rucy); // {name: '루시', age: 15}
console.log(PPomi); // {name: '루시', age: 7}
전개 연산자와 배열/객체의 결합 및 분해
- 함수 매개변수 전개 연산자:
- add 함수는 배열의 요소들을 전개 연산자(...)를 통해 각각의 인수로 전달받아 값을 계산합니다.
출력:
function add(num1, num2, num3) {
return num1 + num2 + num3; // 세 개의 숫자를 더하여 반환
}
console.log(add(10, 20, 30)); // 60 출력 (직접 인수 전달)
console.log(add(10, 20, 30)); // 60
- 배열 결합:
- concat과 전개 연산자를 사용하여 배열을 결합하거나 중간에 새로운 요소를 추가할 수 있습니다.
출력:
const nums = [10, 20, 30]; // nums 배열 선언
console.log(add(nums[0], nums[1], nums[2])); // 배열의 각 요소를 개별 인수로 전달하여 출력
console.log(arr); // ['🍎', '🍓', '🥭', '🍒']
- 객체 병합:
- 전개 연산자를 사용하여 apple 객체에 job 속성을 추가한 new_apple 객체를 생성합니다.
출력:
console.log(add(...nums)); // 전개 연산자를 사용하여 배열 요소를 인수로 전달하여 출력
console.log(new_apple); // {name: '김사과', age: 20, address: {...}, job: '프로그래머'}
활용:
const fruits1 = ['🍎', '🍓']; // 첫 번째 배열 선언
const fruits2 = ['🥭', '🍒']; // 두 번째 배열 선언
let arr = fruits1.concat(fruits2); // concat으로 두 배열을 결합하여 새로운 배열 생성
console.log(arr); // ['🍎', '🍓', '🥭', '🍒'] 출력
console.log('------------');
arr = [...fruits1, ...fruits2]; // 전개 연산자를 사용하여 배열을 결합
console.log(arr); // ['🍎', '🍓', '🥭', '🍒'] 출력
console.log('------------');
arr = [...fruits1, '🍕', ...fruits2]; // 배열 사이에 '🍕'를 추가하여 배열 결합
console.log(arr); // ['🍎', '🍓', '🍕', '🥭', '🍒'] 출력
const apple = {name: '김사과', age: 20, address: {si: '서울시', gu: '동작구', dogn: '상도동'}}; // 중첩 객체 생성
console.log(apple); // 원본 apple 객체 출력
const new_apple = { ...apple, job: '프로그래머' }; // 전개 연산자로 복사하고 새로운 속성 job 추가
console.log(apple); // 원본 apple 객체는 변경되지 않음
console.log(new_apple); // job 속성이 추가된 new_apple 객체 출력
구조 분해 할당
- 배열의 구조 분해 할당:
- 배열의 요소를 순서대로 분해하여 변수에 할당하고, 나머지 요소들을 ...otheres에 할당합니다.
출력:
const fruits = ['🍎', '🍓', '🥭', '🍒']; // 과일 배열 선언
const [fruit1, fruit2, ...otheres] = fruits; // 구조 분해 할당으로 첫 두 항목과 나머지 항목 분리
console.log(fruit1); // '🍎' 출력
console.log(fruit2); // '🍓' 출력
console.log(otheres); // ['🥭', '🍒'] 나머지 항목 출력
console.log(fruit1); // 🍎
console.log(otheres); // ['🥭', '🍒']
- 함수 반환값의 구조 분해 할당:
- sendEmoji 함수의 반환값을 구조 분해하여 각 요소를 개별 변수에 할당합니다.
출력:
function sendEmoji() {
return ['🍎', '🍓', '🥭', '🍒']; // 이모지 배열 반환
}
const [f1, f2, f3, f4] = sendEmoji(); // 함수의 반환값을 구조 분해 할당
console.log(f1); // '🍎' 출력
console.log(f2); // '🍓' 출력
console.log(f3); // '🥭' 출력
console.log(f4); // '🍒' 출력
console.log(f1); // 🍎
- 객체의 구조 분해 할당:
- display 함수는 매개변수를 구조 분해하여 new_apple 객체의 속성을 출력합니다.
출력:
function display({name, age, address, job}) {
// 객체를 매개변수로 받아 구조 분해 할당하여 속성을 개별 변수로 사용
console.log('이름', name); // 이름 출력
console.log('나이', age); // 나이 출력
console.log('주소', address); // 주소 객체 출력
console.log('직업', job); // 직업 출력
}
display(new_apple); // new_apple 객체를 display 함수에 전달하여 속성 출력
display(new_apple); // 이름: 김사과, 나이: 20, 주소: {…}, 직업: 프로그래머
- 기본값 및 별칭:
- 구조 분해 시 pet의 기본값을 설정하고 job 속성을 hobby라는 별칭으로 사용합니다.
출력:
const {name, age, pet = '루시', address, job: hobby} = new_apple; // 기본값 및 별칭 사용하여 구조 분해 할당
console.log(name); // '김사과' 출력
console.log(age); // 20 출력
console.log(pet); // '루시' 출력 (기본값 사용)
console.log(address); // 주소 객체 출력
console.log(hobby); // '프로그래머' 출력 (job을 별칭 hobby로 할당)
console.log(pet); // 루시
객체의 중첩 구조 분해
- 중첩된 객체의 특정 속성만을 구조 분해하여 사용할 수 있습니다.
출력:
const component = {
name: 'button',
styles: {
size: 20,
color: 'black'
}
}; // 중첩 객체 생성
function changeColor({styles: {color}}) {
// 객체를 매개변수로 받아 구조 분해하여 color 속성만 사용
console.log(color); // 'black' 출력
}
changeColor(component); // component 객체의 color 속성 출력
changeColor(component); // black
전체 코드
/*
원시값과 객체값의 비교
원시값 : 값에 의한 복사가 일어남
객체값 : 참조에 의한 복사(메모리 주소)
*/
// 원시값을 이용해 값을 복사해서 넣어줬다
function display(num) {
num = 10; // 함수 내부에서 num의 값을 10으로 변경
console.log(num); // 10 출력 (함수 내부에서만 변경됨)
}
const value = 5; // 글로벌 스코프에 value라는 원시값 변수 선언
display(value); // display 함수 호출, 값 복사에 의한 호출
console.log(value); // 5 출력 (원본 값은 변하지 않음)
console.log('------------');
// 객체값 이용해 원본의 값을 변경해서 넣어줬다
function displayObj(obj) {
obj.age = 15; // 함수 내부에서 객체의 age 속성을 15로 변경
console.log(obj); // {name: '루시', age: 15} 출력 (참조에 의한 복사이므로 원본도 변경됨)
}
const Rucy = {name: '루시', age: 10}; // Rucy라는 객체를 생성
displayObj(Rucy); // displayObj 함수 호출, 객체의 참조값 전달
console.log(Rucy); // {name: '루시', age: 15} 출력 (함수에서 변경된 원본 객체)
console.log('------------');
function changeAge(obj) {
return { ...obj, age: 7 }; // 새로운 객체를 반환하며, age를 7로 설정 (원본은 유지됨)
}
const PPomi = changeAge(Rucy); // Rucy 객체를 changeAge에 전달하여 age가 7인 새 객체 생성
console.log(Rucy); // {name: '루시', age: 15} 원본 객체는 변하지 않음
console.log(PPomi); // {name: '루시', age: 7} 새롭게 생성된 객체 출력
console.log('------1------');
function add(num1, num2, num3) {
return num1 + num2 + num3; // 세 개의 숫자를 더하여 반환
}
console.log(add(10, 20, 30)); // 60 출력 (직접 인수 전달)
console.log('------2------');
const nums = [10, 20, 30]; // nums 배열 선언
console.log(add(nums[0], nums[1], nums[2])); // 배열의 각 요소를 개별 인수로 전달하여 출력
console.log('------3------');
console.log(add(...nums)); // 전개 연산자를 사용하여 배열 요소를 인수로 전달하여 출력
console.log('------4------');
const fruits1 = ['🍎', '🍓']; // 첫 번째 배열 선언
const fruits2 = ['🥭', '🍒']; // 두 번째 배열 선언
let arr = fruits1.concat(fruits2); // concat으로 두 배열을 결합하여 새로운 배열 생성
console.log(arr); // ['🍎', '🍓', '🥭', '🍒'] 출력
console.log('------5------');
arr = [...fruits1, ...fruits2]; // 전개 연산자를 사용하여 배열을 결합
console.log(arr); // ['🍎', '🍓', '🥭', '🍒'] 출력
console.log('------6------');
arr = [...fruits1, '🍕', ...fruits2]; // 배열 사이에 '🍕'를 추가하여 배열 결합
console.log(arr); // ['🍎', '🍓', '🍕', '🥭', '🍒'] 출력
console.log('------7------');
const apple = {name: '김사과', age: 20, address: {si: '서울시', gu: '동작구', dogn: '상도동'}}; // 중첩 객체 생성
console.log(apple); // 원본 apple 객체 출력
const new_apple = { ...apple, job: '프로그래머' }; // 전개 연산자로 복사하고 새로운 속성 job 추가
console.log(apple); // 원본 apple 객체는 변경되지 않음
console.log(new_apple); // job 속성이 추가된 new_apple 객체 출력
console.log('------8------');
const fruits = ['🍎', '🍓', '🥭', '🍒']; // 과일 배열 선언
const [fruit1, fruit2, ...otheres] = fruits; // 구조 분해 할당으로 첫 두 항목과 나머지 항목 분리
console.log(fruit1); // '🍎' 출력
console.log(fruit2); // '🍓' 출력
console.log(otheres); // ['🥭', '🍒'] 나머지 항목 출력
console.log('------9------');
function sendEmoji() {
return ['🍎', '🍓', '🥭', '🍒']; // 이모지 배열 반환
}
const [f1, f2, f3, f4] = sendEmoji(); // 함수의 반환값을 구조 분해 할당
console.log(f1); // '🍎' 출력
console.log(f2); // '🍓' 출력
console.log(f3); // '🥭' 출력
console.log(f4); // '🍒' 출력
console.log('------10------');
function display({name, age, address, job}) {
// 객체를 매개변수로 받아 구조 분해 할당하여 속성을 개별 변수로 사용
console.log('이름', name); // 이름 출력
console.log('나이', age); // 나이 출력
console.log('주소', address); // 주소 객체 출력
console.log('직업', job); // 직업 출력
}
display(new_apple); // new_apple 객체를 display 함수에 전달하여 속성 출력
console.log('------10------');
const {name, age, pet = '루시', address, job: hobby} = new_apple; // 기본값 및 별칭 사용하여 구조 분해 할당
console.log(name); // '김사과' 출력
console.log(age); // 20 출력
console.log(pet); // '루시' 출력 (기본값 사용)
console.log(address); // 주소 객체 출력
console.log(hobby); // '프로그래머' 출력 (job을 별칭 hobby로 할당)
console.log('------10------');
const component = {
name: 'button',
styles: {
size: 20,
color: 'black'
}
}; // 중첩 객체 생성
function changeColor({styles: {color}}) {
// 객체를 매개변수로 받아 구조 분해하여 color 속성만 사용
console.log(color); // 'black' 출력
}
changeColor(component); // component 객체의 color 속성 출력
Promise 관련 메서드
1. 기본 함수 정의
getBanana()
- 기능: 1초 후에 바나나 이모지(🍌)를 반환하는 Promise를 생성합니다.
function getBanana() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('🍌');
}, 1000);
});
}
getApple()
- 기능: 3초 후에 사과 이모지(🍎)를 반환하는 Promise를 생성합니다.
function getApple() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('🍎');
}, 3000);
});
}
getOrange()
- 기능: 즉시 에러를 반환하는 Promise를 생성합니다.
function getOrange() {
return Promise.reject(new Error('오렌지 없음'));
}
1. Promise를 사용한 병렬 실행
getBanana()
.then((banana) => getApple().then((apple) => [banana, apple]))
.then(console.log);
설명:
- getBanana()가 1초 후에 실행되어 🍌를 반환합니다.
- 이후 getApple()이 실행되어 3초 후에 🍎를 반환합니다.
- getBanana와 getApple은 병렬로 실행되지 않기 때문에 전체 실행 시간은 4초가 됩니다.
2. Promise.all : 병렬적으로 여러 Promise 실행
Promise.all은 여러 Promise를 병렬로 실행하며, 모든 Promise가 완료되면 결과를 배열로 반환합니다. 하나라도 실패하면 전체가 에러로 처리됩니다.
Promise.all([getBanana(), getApple()])
.then((fruits) => console.log('all', fruits)) // 3초 후 ['🍌', '🍎'] 출력
.catch(console.log);
설명:
- getBanana()와 getApple()을 동시에 실행하여 가장 긴 3초가 지나면 결과가 반환됩니다.
- ['🍌', '🍎']가 출력됩니다.
실패 예제
Promise.all([getBanana(), getApple(), getOrange()])
.then((fruits) => console.log('all', fruits))
.catch(console.log);
설명:
- getOrange()는 에러를 반환하므로 전체 Promise가 실패하며 catch로 이동해 에러 메시지를 출력합니다.
3.Promise.race : 가장 빨리 완료된 Promise 반환
Promise.race는 여러 Promise 중 가장 빨리 완료된 Promise의 결과를 반환합니다.
Promise.race([getBanana(), getApple()])
.then((fruit) => console.log('race', fruit));
설명:
- getBanana()와 getApple()이 동시에 실행됩니다.
- getBanana()가 1초로 가장 빨리 완료되므로 🍌가 출력됩니다.
5. Promise.allSettled : 여러 Promise를 병렬로 처리, 실패한 것도 결과 반환
Promise.allSettled는 여러 Promise를 병렬로 실행하고, 모든 Promise의 이행 여부와 결과를 반환합니다. 실패한 Promise도 상태와 이유를 함께 반환합니다.
Promise.allSettled([getBanana(), getApple(), getOrange()])
.then((fruits) => console.log('allSettled', fruits));
설명:
- 모든 Promise가 완료될 때까지 기다리며, 성공 여부와 관계없이 각각의 결과를 반환합니다.
- 결과는 fulfilled 상태와 rejected 상태가 함께 포함됩니다.
출력 예:
[
{ status: "fulfilled", value: "🍌" },
{ status: "fulfilled", value: "🍎" },
{ status: "rejected", reason: Error("오렌지 없음") }
]
- Promise.all : 모든 Promise가 성공해야 전체 결과를 반환. 하나라도 실패 시 전체가 실패.
- Promise.race : 가장 빠르게 완료된 Promise의 결과만 반환.
- Promise.allSettled : 모든 Promise의 성공, 실패 여부와 상관없이 각각의 결과를 반환.
전체 코드
// 1초 후에 '🍌' 바나나 이모지를 반환하는 Promise를 생성하는 함수
function getBanana() {
return new Promise((resolve) => { // 새로운 Promise 객체 생성
setTimeout(() => { // 1초 후 실행
resolve('🍌'); // 1초 후 '🍌' 반환
}, 1000); // 1000ms(1초)
});
}
// 3초 후에 '🍎' 사과 이모지를 반환하는 Promise를 생성하는 함수
function getApple() {
return new Promise((resolve) => { // 새로운 Promise 객체 생성
setTimeout(() => { // 3초 후 실행
resolve('🍎'); // 3초 후 '🍎' 반환
}, 3000); // 3000ms(3초)
});
}
// 에러를 발생시키는 Promise를 생성하는 함수 (즉시 실패)
function getOrange() {
return Promise.reject(new Error('오렌지 없음')); // 에러 메시지와 함께 즉시 실패 처리
}
// 콜백 중첩을 사용하여 바나나와 사과를 순차적으로 가져옴
getBanana() // getBanana 호출, 1초 후 '🍌'을 반환
.then((banana) => getApple().then((apple) => [banana, apple])) // 3초 후 '🍎' 추가로 반환
.then(console.log); // ['🍌', '🍎'] 출력
// Promise.all: 모든 Promise를 병렬로 실행하고 모두 성공 시 결과 배열 반환
Promise.all([getBanana(), getApple()]) // getBanana와 getApple을 병렬로 실행
.then((fruits) => console.log('all', fruits)) // ['🍌', '🍎'] 출력 (가장 오래 걸린 3초 후)
.catch(console.log); // 실패 시 에러를 출력
// Promise.all: 여러 Promise 중 하나라도 실패하면 전체가 실패로 처리됨
Promise.all([getBanana(), getApple(), getOrange()]) // getBanana, getApple, getOrange 병렬 실행
.then((fruits) => console.log('all', fruits)) // 성공 시 ['🍌', '🍎'] 출력
.catch(console.log); // getOrange가 실패하므로 catch로 에러 출력
// Promise.race: 가장 빨리 완료된 Promise의 결과만 반환
Promise.race([getBanana(), getApple()]) // 가장 빠른 getBanana가 완료됨
.then((fruit) => console.log('race', fruit)); // 'race 🍌' 출력 (1초 후)
// Promise.allSettled: 모든 Promise를 병렬로 실행하고 각각의 결과(성공/실패 여부 포함)를 배열로 반환
Promise.allSettled([getBanana(), getApple(), getOrange()]) // 병렬로 실행
.then((fruits) => console.log('allSettled', fruits)) // 모든 결과 출력 (상태 포함)
.catch(console.log); // 에러가 발생해도 실행되지 않음
'백엔드 > Node.js' 카테고리의 다른 글
Node.js 프로젝트 시작하기: 효율적인 파일 구조와 라우팅 관리 (20) | 2024.11.13 |
---|---|
package.json 파일 생성 및 설정 방법 (0) | 2024.11.13 |
fetch (2) | 2024.11.09 |
Node.js (2) | 2024.11.08 |
async와 await (8) | 2024.11.07 |