[JS] Promise

Tae_Tae·2024년 8월 21일

JavaScript는 싱글 스레드 기반 언어로, 한 번에 하나의 작업만 처리한다.
하지만 웹 환경에서는 서버 통신, 파일 읽기, 타이머처럼 시간이 오래 걸리는 작업들이 많은데,
이러한 작업들이 동기적으로 처리되면, UI나 다른 작업이 멈추거나 느려질 수 있기 때문에
비동기 처리(Asynchronous) 방식이 필요하고, 이를 효율적으로 다루기 위해 Promise 객체를 사용한다.

기본 개념


Promise는 “미래에 완료될 작업에 대한 약속”을 표현하는 비동기 처리 객체로
비동기 작업의 성공/실패 결과를 나중에 처리할 수 있도록 상태를 갖고 흐름을 제어합니다.

const myPromise = new Promise((resolve, reject) => {
    // 비동기 작업 수행
    let success = true; // 예시 조건
    if (success) {
        resolve("작업이 성공했습니다."); // 작업 성공 시 호출
    } else {
        reject("작업이 실패했습니다."); // 작업 실패 시 호출
    }
});
  • resolve(value) → 작업이 성공했을 때 결과를 반환
  • reject(reason) → 작업이 실패했을 때 에러를 반환

Promise의 상태


상태설명
pending대기 상태 (아직 작업이 끝나지 않은 초기 상태)
fulfilled성공 완료 상태 (resolve() 호출됨)
rejected실패 상태 (reject() 호출됨)

Pending 상태에서 시작하며, 이후 Fulfilled 또는 Rejected 상태로 변경된다.

.then(), .catch(), .finally() 사용


Promise 객체는 then()catch() 메서드를 사용하여 이행되거나 거부된 이후의 처리를 정의할 수 있다.

myPromise
    .then((result) => {
        console.log(result); // "작업이 성공했습니다."
    })
    .catch((error) => {
        console.error(error); // "작업이 실패했습니다."
    });
	.finally(() => {
      console.log("작업 종료 (성공/실패 상관없이)");
    });

위 예시 코드는 myPromise가 성공적으로 이행되면 then 블록이 실행되며, 거부되면 catch 블록이 실행된다. (finally는 항상 실행)

then (onFulfilled, onRejected)

Promise가 이행되었을 때, 또는 거부되었을 때 각각 호출될 콜백 함수를 지정한다.
이 메서드는 또 다른 Promise를 반환하므로, then 체인을 사용하여 여러 비동기 작업을 순차적으로 처리할 수 있다.

myPromise.then(result => {
  console.log("성공 결과:", result);
});

catch(onRejected)

Promise가 거부되었을 때 호출될 콜백 함수를 지정한다.
주로 에러 처리를 위해 사용한다.

myPromise.catch(error => {
  console.error("에러 발생:", error);
});

.finally()

Promise의 성공/실패 관계없이 항상 실행한다.

myPromise.finally(() => {
  console.log("작업 종료 (성공/실패 상관없이)");
});

fetch와 함께 쓰는 Promise

  • fetch()는 항상 Promise를 반환한다.
  • HTTP 요청 성공 여부에 따라 .then(), .catch()로 처리 가능하다.
fetch("https://api.example.com/data")
  .then(response => {
    if (!response.ok) throw new Error("응답 실패");
    return response.json();
  })
  .then(data => console.log("데이터:", data))
  .catch(error => console.error("에러:", error))
  .finally(() => console.log("요청 종료"));

Promise 체이닝


Promisethen() 메서드로 체이닝이 가능하여, 순차적으로 비동기 작업을 처리할 수 있다.
체이닝을 통해 여러 비동기 작업을 순차적으로 실행하고, 중간 단계에서 발생한 에러를 처리할 수 있다.

new Promise((resolve, reject) => {
    resolve(1);
})
.then((result) => {
    console.log(result); // 1
    return result * 2;
})
.then((result) => {
    console.log(result); // 2
    return result * 3;
})
.then((result) => {
    console.log(result); // 6
})
.catch((error) => {
    console.error(error);
});

위 예제에서는 Promise 체이닝을 통해 각각의 then 블록에서 결과를 처리하고, 다음 단계로 전달하고 있다.

Promise 유틸 메서드들

Promise.all()


Promise.all() 메서드는 여러 개의 Promise를 병렬로 처리할 때 유용한데 주어진 모든 Promise가 이행될 때까지 기다린 후, 그 결과를 배열로 반환한다.
하나라도 거부되면 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); // [3, 42, "foo"]
});

Promise.all()은 병렬로 여러 비동기 작업을 수행하고, 모든 작업이 완료된 후에 결과를 한 번에 처리하는 데 유용하다.

Promise.race()


Promise.race() 메서드는 여러 개의 Promise 중 가장 먼저 이행되거나 거부된 Promise의 결과를 반환한다.

const promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
    console.log(value); // "two"
});

위 예제를 보면 promise2가 더 빨리 이행되므로 Promise.race()"two"를 출력한다.

Promise.finally()


Promise.finally() 메서드는 Promise가 이행되든 거부되든, 마지막에 실행되어야 할 코드를 작성할 때 사용한다.

(finally 블록은 결과에 관계없이 항상 실행됨)

myPromise
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.error(error);
    })
    .finally(() => {
        console.log("Promise가 완료되었습니다."); // 항상 실행
    });

이 메서드는 리소스 해제, 로딩 상태 해제 등 반드시 수행해야 할 작업을 정의하는 데 유용합니다.

Promise 체이닝 에러 처리 주의점


Promise 내부에서 발생한 에러는 반드시 .catch() 또는 try/catch로 처리해야 한다.

Promise 체이닝에서 에러가 발생하면, 발생 지점에서 가장 가까운 catch() 블록으로 에러가 전달되는데 catch() 블록을 여러 개 사용할 수도 있지만, 마지막에 하나만 두는 것이 일반적인 방법이다.

new Promise((resolve, reject) => {
    throw new Error("에러 발생!");
})
.then((result) => {
    console.log(result);
})
.catch((error) => {
    console.error("에러를 잡았습니다:", error.message); // "에러를 잡았습니다: 에러 발생!"
})
.finally(() => {
    console.log("작업 완료");
});

위 예시에서는 Promise 내에서 에러가 발생했으나, catch() 블록에서 이를 처리하고, finally() 블록에서 추가 작업을 수행한다.

정리


개념설명
Promise미래에 결과가 나오는 비동기 작업을 다루는 객체
상태pending → fulfilled 또는 rejected
then성공 시 실행되는 콜백
catch실패 시 실행되는 콜백
finally무조건 실행되는 정리용 콜백
all모든 Promise가 성공해야 완료
race가장 먼저 완료된 Promise 하나만 처리
fetch서버 요청 시 자주 쓰이며 Promise 기반으로 응답 처리

0개의 댓글