All'alba vincerò

At dawn, I will win!

Javascript

[JS] Promise: 비동기 작업을 처리하는 객체

나디아 Nadia 2024. 6. 27. 00:57

목차

  • Promise
  • Promise 메서드
    1. then()
    2. catch()
    3. finally()
    4. Promise.all
    5. Promise.race
  • Promise 체이닝

📌 Promise

: 비동기 작업을 처리하고 결과를 관리하는 데 사용되는 객체

let promise = new Promise(function(resolve, reject) {
                                     executor
});
  • 비동기 작업(네트워크 요청, 파일 읽기, 타이머 등)을 처리할 때 사용
  • 성공하면 then(), 실패하면 catch() 실행
  • 실제 promise에는 setTitmeout()이 필요 없음
    (예시에서는 비동기를 구현하기 위해 사용됨)

 

 

👍 promise 장점

  1. 비동기 작업의 가독성 향상
    - 콜백 지옥을 줄여줌
  2. 에러 처리의 일관성
    - Promise 체이닝을 통해 체인의 어느 부분에서 발생한 에러든지 한 곳에서 처리할 수 있다.
  3. 비동기 작업의 순차적 실행
    비동기 작업을 처리하는 순서를 보장할 수 있다.
  4. 다양한 비동기 작업 관리
    - 여러 비동기 작업을 동시에 처리할 수 있는 메서드들을 제공한다. 

 


 

Promise 생성

new Promise()

 

 

 

executor(실행자, 실행 함수)

: new Promise에 전달되는 함수

  • executor는 new Promise가 만들어질 때 자동으로 실행
  • 결과를 최종적으로 만들어내는 제작 코드를 포함함
  • executor는 콜백 함수를 받는데 콜백 함수의 매개 변수가 함수(resolve, reject)로 정해져있다.
    (변수명은 바꿀 수 있음⭕, but 순서(성공, 실패)는 지켜야 함❗)
    • resolve(value) 
      : 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출
    • reject(error)
      : 에러 발생 시 에러 객체를 나타내는 error와 함께 호출

 

 

 

promise 객체의 내부 프로퍼티

  • new Promise 생성자가 반환하는 promise 객체는 내부 프로퍼티를 갖는다. 
  • state
    - 처음엔 "pending"(보류)
    - resolve가 호출되면 "fulfilled"
    - reject가 호출되면 "rejected"

  • result
    - 처음엔 "undefined"
    - resolve가 호출되면 "value"
    - reject가 호출되면 "error"

 

 

* Promise 값에는 접근 불가

const p = new Promise((resolve, reject)=>{
           ...
});

console.log(p.PromiseState); // X

 

 


📌 Promise 메서드

  1. then()
  2. catch()
  3. finally()
  4. Promise.all()
  5. Promise.race()

 

 

📍 then 메서드

promise 객체.then(
              ...
);
  • Promise가 성공했을 때 실행될 콜백 함수(resolve)와 실패했을 때 실행될 콜백 함수(reject)를 인수로 받음
  • Promise 체이닝 가능⭕
    (또 다른 Promise를 반환하여 연속으로 사용)

  • 두 개의 콜백 함수를 인수로 받음
    - 첫 번째 콜백 함수: Promise가 성공적으로 완료되었을 때(resolved) 실행
    - 두 번째 콜백 함수: Promise가 실패했을 때(rejected) 실행
// 새로운 Promise 생성
let promise = new Promise(function(resolve, reject) {
    // 비동기 작업을 시뮬레이션 (예: 서버 요청)
    setTimeout(function() {
        let success = true; // 성공 여부
        if (success) {
            resolve("작업이 성공했습니다!"); // 작업 성공 시
        } else {
            reject("작업이 실패했습니다."); // 작업 실패 시
        }
    }, 2000); // 2초 후에 실행
});


// then 메서드를 사용하여 결과 처리***
promise.then(
    function(successMessage) { // 성공 시 실행
        console.log(successMessage);
    },
    
    function(errorMessage) { // 실패 시 실행
        console.log(errorMessage);
    }
);

 

 

 

 

✨ then 메서드에서 return의 역할

  • 비동기 작업을 체인으로 연결하고, 다음 then에서 이전 then의 결과 사용
  • return을 사용하면⭕, 다음 then에서 반환된 값을 사용할 수 있다.
    (새로운 Promise 객체 반환)
  • return을 사용하지 않으면❌, undefined가 전달된다. 
  • then 메서드에서 명시적으로 값을 반환하면, 그 값이 다음 then 메서드의 PromiseResult 값이 된다

 

* 명시적으로 리턴값을 설정하는 경우

let promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve(1); // 1초 후에 1 반환
    }, 1000);
});

promise
    .then(function(result) {
        console.log(result); // 1 출력
        return result * 2; // 2를 반환
    })
    .then(function(result) {
        console.log(result); // 2 출력
        return result * 3; // 6을 반환
    })
    .then(function(result) {
        console.log(result); // 6 출력
    });

 

 

명시적으로 값을 반환하지 않은 경우

let promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve(1); // 1초 후에 1 반환
    }, 1000);
});

promise
    .then(function(result) {
        console.log(result); // 1 출력
        // 값을 반환하지 않음
    })
    .then(function(result) {
        console.log(result); // undefined 출력
    });

 

 


📍 catch 메서드

: Promise가 실패했을 때(rejected) 실행될 함수를 정의

  • Promise then 메서드 체인에서 에러가 발생했을 때 그 에러를 잡아 처리하는데 사용
  • 체인의 끝에서 사용하여 모든 이전 단계에서 발생한 에러를 한 번에 처리
  • 체인의 중단 방지
    - 에러가 발생하더라도 이후에 계속해서 then 메서드를 호출할 수 있도록 체인을 유지
let promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve(1); // 1초 후에 1 반환
    }, 1000);
});

promise
    .then(function(result) {
        console.log(result); // 1 출력
        throw new Error("문제 발생!"); // 에러 발생
    })
    .catch(function(error) {
        console.error("에러:", error.message); // "에러: 문제 발생!" 출력
        return 2; // 새로운 값을 반환
    })
    .then(function(result) {
        console.log(result); // 2 출력
    });

// 첫 번째 then에서 에러를 던지지만, catch 메서드가 이 에러를 처리하고 새로운 값을 반환한다. 
// 그 결과, 체인이 중단되지 않고 다음 then이 호출된다.

 

 


📍 finally 메서드

: Promise의 상태(resolve, reject)에 관계없이 무조건 실행되어야 하는 코드를 넣기 위해 사용

  • Promise가 이행(fulfilled)되거나 거부(rejected)된 후에 항상 실행되는 코드를 작성할 때 사용
  • 특정 작업이 완료된 후에 수행할 정리 작업(clean-up)을 실행할 때 유용
  • 후속 작업(로딩 상태 해제, 자원 정리, 로그 기록 등)에 사용

 

 

finally 메서드의 특징

  1. 항상 실행
    - finally 블록은 Promise가 이행되든 거부되든 무조건 실행된다. 


  2. 인수 없음
    - finally 메서드의 콜백 함수는 인수를 받지 않음

    즉, finally 블록 내부에서는 Promise의 결과나 에러에 접근할 수 없다❌

  3. 연결된 Promise 반환
    - finally 메서드는 원래의 Promise를 반환
    따라서 finally 블록 이후에도 추가적인 then이나 catch 메서드를 체인으로 연결할 수 있다⭕
let promise = new Promise((resolve, reject) => { // 비동기 작업 수행
  setTimeout(() => reject(new Error("실패!")), 1000);
});


promise
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error(error); // "실패!" 출력
  })
  .finally(() => {
    console.log("Promise가 완료되었습니다."); // 무조건 실행
  });
// Promise가 거부되었기 때문에 catch 블록이 실행되고, 이후에 finally 블록이 실행됨

 

 


📍 Promise.all

  • 연결된 Promise들이 모두 처리된 다음에 그 결과를 배열로 반환, then/catch 실행
  • 모든 Promise들의 반환값들의 배열을 반환 (then의 매개변수 result)
    • 여러 개의 Promise를 iterable 객체(ex 배열)에 담아서 실행하기 때문
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    console.log(results); // [1, 2, 3]
  })
  .catch((error) => {
    console.error(error); // 만약 하나라도 실패하면 이 블록이 실행됨
  });

 

 

 

 

📍 Promise.race

  • 가장 먼저 완료된 Promise의 결과만 반환 (then의 매개변수 result)
    (Promise들이 경주해서 제일 빠른 것만 출력)

    • 여러 개의 Promise를 iterable 객체(ex 배열)에 담아서 실행하기 때문
const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'first'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'second'));

Promise.race([promise1, promise2])
  .then((result) => {
    console.log(result); // 'second' (promise2가 먼저 완료됨)
  })
  .catch((error) => {
    console.error(error); // 실패한 경우 이 블록이 실행됨
  });

 

 


📌 Promise 체이닝(promise chaining)

: 여러 비동기 작업을 순차적으로 실행하고, 각 작업이 완료된 후 다음 작업을 실행

  • 각 프라미스가 완료될 때마다 .then() 메서드를 사용해 다음 프라미스를 반환
    - 이렇게 하면 각 프라미스가 순차적으로 실행되고, 마지막 프라미스가 완료될 때까지 기다린다. 
    - Promise의 result가 .then() 핸들러의 연결을 통해 전달되는 점을 이용

  • catch() 블록을 사용하여 체인의 모든 프라미스에서 발생한 에러를 한 곳에서 처리할 수 있다⭕
  • 각 단계에서 결과를 받아 다음 단계에 전달할 수 있어, 비동기 작업 간의 의존성을 쉽게 관리할 수 있다⭕

 

// 첫 번째 비동기 작업을 수행하는 함수
function firstAsyncTask() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("첫 번째 작업 완료");
            resolve("첫 번째 결과");
        }, 1000);
    });
}

// 두 번째 비동기 작업을 수행하는 함수
function secondAsyncTask(previousResult) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("두 번째 작업 완료: " + previousResult);
            resolve("두 번째 결과");
        }, 1000);
    });
}

// 세 번째 비동기 작업을 수행하는 함수
function thirdAsyncTask(previousResult) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("세 번째 작업 완료: " + previousResult);
            resolve("세 번째 결과");
        }, 1000);
    });
}

// 프라미스 체이닝 시작
firstAsyncTask()
    .then(result => {
        return secondAsyncTask(result);
    })
    .then(result => {
        return thirdAsyncTask(result);
    })
    .then(result => {
        console.log("모든 작업 완료: " + result);
    })
    .catch(error => {
        console.error("에러 발생: " + error);
    });


// firstAsyncTask() 함수가 호출되고 프라미스를 반환

// 첫 번째 프라미스가 완료되면 then 블록이 실행, secondAsyncTask 함수가 호출되어 새로운 프라미스를 반환
// 이때 firstAsyncTask의 결과가 secondAsyncTask에 전달

// 두 번째 프라미스가 완료되면 다시 then 블록이 실행, thirdAsyncTask 함수가 호출되어 또 다른 프라미스를 반환 
// 이번에는 secondAsyncTask의 결과가 thirdAsyncTask에 전달

// 세 번째 프라미스가 완료되면 마지막 then 블록이 실행, 최종 결과 출력

// 만약 어느 프라미스에서든 에러가 발생하면, catch 블록이 실행되어 에러가 출력

 

 

예시 2

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
});

// 1 2 4

// (*) 첫 번째 .then은 1을 출력하고 new Promise(…)를 반환((*))
// 1초 후 이 프라미스가 이행
// 그 결과(resolve의 인수인 result * 2)는 두 번째 .then((**))으로 전달

// (**) 두 번째 핸들러((**))는 2를 출력 new Promise(…)를 반환((**))
// 1초 후 이 프라미스가 이행
// 그 결과(resolve의 인수인 result * 2)는 세 번째 .then((***))으로 전달

 

 


 

📢 콜백 함수  vs  Promise

  콜백 함수 Promise
코드 가독성 가독성👎
"콜백 지옥" ⛔
가독성 👍
유지보수가 쉬운 코드⭕
에러 처리 각 콜백 함수에서 에러를 처리해야 개별적으로 하므로 에러 처리가 분산, 일관성👎 catch 메서드로 체인 전체의 에러를 일관되게 처리 👍
비동기 작업의 순차적 처리 중첩된 콜백 함수를 사용해야 해서
코드 가독성👎
then 메서드로 순차적으로 비동기 작업 처리
여러 비동기 작업의 병렬 처리 병렬 처리를 위해서는 
추가적인 논리와 구조가 필요
Promise.all과 Promise.race
여러 비동기 작업을 쉽게 병렬 처리

 

 


 

 

프라미스

 

ko.javascript.info

 

 

프라미스 체이닝

 

ko.javascript.info

 

 

Promise.all() - JavaScript | MDN

Promise.all() 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환합니다. 주어진 프로미스 중 하나가 거부하는 경우, 첫

developer.mozilla.org

 

 

Promise.race() - JavaScript | MDN

Promise.race() 메소드는 Promise 객체를 반환합니다. 이 프로미스 객체는 iterable 안에 있는 프로미스 중에 가장 먼저 완료된 것의 결과값으로 그대로 이행하거나 거부합니다.

developer.mozilla.org