본문 바로가기
백엔드/Node.js

Promise

by hyunji00pj 2024. 11. 7.
 

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를 사용한다

원시값과 객체값의 비교

  1. 원시값 복사:
    • 원시값은 값에 의한 복사로, 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
  1. 객체값 복사:
    • 객체는 참조에 의한 복사로, 함수 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}

 

  1. 객체의 불변성 유지:
    • 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}

 

전개 연산자와 배열/객체의 결합 및 분해

  1. 함수 매개변수 전개 연산자:
    • add 함수는 배열의 요소들을 전개 연산자(...)를 통해 각각의 인수로 전달받아 값을 계산합니다.

출력:

function add(num1, num2, num3) {
    return num1 + num2 + num3; // 세 개의 숫자를 더하여 반환
}

console.log(add(10, 20, 30)); // 60 출력 (직접 인수 전달)
console.log(add(10, 20, 30)); // 60
  1. 배열 결합:
    • concat과 전개 연산자를 사용하여 배열을 결합하거나 중간에 새로운 요소를 추가할 수 있습니다.

출력:

const nums = [10, 20, 30]; // nums 배열 선언
console.log(add(nums[0], nums[1], nums[2])); // 배열의 각 요소를 개별 인수로 전달하여 출력
console.log(arr); // ['🍎', '🍓', '🥭', '🍒']
  1. 객체 병합:
    • 전개 연산자를 사용하여 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 객체 출력

구조 분해 할당

  1. 배열의 구조 분해 할당:
    • 배열의 요소를 순서대로 분해하여 변수에 할당하고, 나머지 요소들을 ...otheres에 할당합니다.

출력:

const fruits = ['🍎', '🍓', '🥭', '🍒']; // 과일 배열 선언
const [fruit1, fruit2, ...otheres] = fruits; // 구조 분해 할당으로 첫 두 항목과 나머지 항목 분리
console.log(fruit1); // '🍎' 출력
console.log(fruit2); // '🍓' 출력
console.log(otheres); // ['🥭', '🍒'] 나머지 항목 출력
console.log(fruit1); // 🍎
console.log(otheres); // ['🥭', '🍒']
  1. 함수 반환값의 구조 분해 할당:
    • 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); // 🍎
  1. 객체의 구조 분해 할당:
    • 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, 주소: {…}, 직업: 프로그래머
  1. 기본값 및 별칭:
    • 구조 분해 시 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