Promise 객체를 이해해보자

limhi·2024년 9월 10일
0
post-thumbnail

async/await 을 평소 잘 써왔지만, 그 기반이 되는 Promise 에 대해 더 이해해보고자 글을 작성하였습니다.

Promise 란,

Promise 란, Promise 가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자 입니다. 비동기 연산이 종료된 이후에 결과 값이 promise 객체에 저장되며, 성공과 실패를 처리하기 위한 처리기를 연결할 수 있습니다.

const myPromise = new Promise(function(resolve, reject));

new Promise 에 전달되는 함수는 excutor 라고 부릅니다. 이는 new Promise 가 만들어질 때 자동으로 실행되는데, 결과를 최종적으로 만들어내는 코드를 포함합니다.

excutor 의 인수 resolvereject 는 자바스크립트에서 자체 제공하는 콜백 함수입니다.

  • resolve (value)
    : 일이 성공적으로 끝난 경우, 그 결과를 나타내는 value와 함께 호출

  • reject (error)
    : 에러 발생 시 에러 객체를 나타내는 error와 함께 호출

executor 에선 결과를 즉시 얻든 늦게 얻든 상관없이 상황에 따라 인수로 넘겨준 콜백 중 하나를 반드시 호출해야 합니다. 그리고 이때 변경된 상태는 더 이상 변하지 않습니다.

const myPromise = new Promise((resolve, reject) => {
  resolve("completed");
  
  reject(new Error("error!!")); // 무시됨
  setTimeout(() => resolve("completed again")); // 무시됨
});

더하여 resolve 나 reject 는 인수를 하나만 받고 (혹은 아무것도 받지 않고) 그 이외의 인수는 무시한다는 특성도 있습니다.

Promise 내부 프로퍼티

new Promise 생성자가 반환하는 promise 객체 내부 프로퍼티는 다음과 같습니다.

  • state
    :처음엔 "pending" 이었다가 resolve가 호출되면 "fulfilled", reject가 호출되면 "rejected"로 변화

  • result
    :처음엔 undefined이었다가 resolve가 호출되면 value로, reject가 호출되면 error로 변화

const myPromise = new Promise ((resolve, reject) => {
  // promise가 만들어지면 excutor 함수 자동 실행
  setTimeout(() => resolve("완료"), 1000);
});

위 예시 코드를 살펴보았을 때 알 수 있는 것은 두 가지 입니다.

  1. excutor는 new Promise 에 의해 자동으로, 그리고 즉각적으로 호출됩니다.

  2. excutor는 인자로 resolve와 reject 함수를 받습니다. 이 함수들은 자바스크립트 엔진이 미리 정의한 함수이므로 따로 만들 필요가 없습니다.

위 excutor 함수가 시작되고 1초 후, resolve("완료") 가 호출되고 결과가 만들어집니다.

이렇게 성공적으로 처리된 경우, 프로미스는 "fulfilled promise" 라고 부릅니다.

반면 reject 의 상황을 살펴봅시다.

const myPromise = new Promise ((resolve, reject) => {
  setTimeout(() => reject(new Error("에러 발생")), 1000);
});

1초 후, reject(...) 이 호출되면 프로미스의 상태가 rejected 로 변경됩니다.

즉, excutor 는 보통 시간이 걸리는 일을 수행하고 일이 끝나면 resolve 나 reject 함수를 호출하는데, 이 때 프로미스 객체의 상태가 변화합니다.

resolve 또는 rejected 상태의 프로미스를 "settled promise" 라고 부르며, 이는 "pending" 상태와 반대되는 경우입니다.

then, catch, finally

프로미스 객체의 상태가 변화하고 이에 따른 후속처리를 해야합니다. 이러한 후속 처리를 위해 프로미스는 후속 메소드인 then, catch, finally 를 제공합니다.

then

then은 프로미스에서 가장 중요하고 기본이 되는 메소드이며 다음과 같이 활용할 수 있습니다

myPromise.then(
  (result) => { 
    // result를 다루는 함수 작성 
  },
  (error) => { 
    // error를 다루는 함수 작성 
  }
);

첫번째 인수는 프로미스가 이행되었을 때 실행되는 함수이며, 실행결과를 받습니다.
두번째 인수는 프로미스가 거부되었을 때 받는 함수이며, 에러를 받습니다.

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => resolve("완료"), 1000);
});

myPromise.then(
  result => alert(result),
  error => alert(error) // => 실행X
)

catch

에러가 발생한 경우에만 다루고 싶다면 .then(null, errorHandlingFunction).catch(errorHandlingFunction) 을 쓸 수 있습니다. 이 둘은 동일하게 동작합니다.

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("error!!")), 1000);
});

myPromise.catch(alert); // 1초 후 error 출력

.catch(f) 는 문법이 간결하다는 점만 빼고 .then(null,f) 과 완벽하게 같습니다.

finally

try...catch 문에 finally 절이 있는 것처럼 프로미스에도 finally 절이 있습니다. 프로미스가 처리되면 f 가 항상 실행되므로 .then(f, f) 와 유사합니다.

즉, 성공 실패 여부 상관없이 동작합니다.
쓸모가 없어진 로딩 인디케이터(loading indicator)를 멈추는 경우같이, 결과가 어떻든 마무리가 필요하면 finally가 유용합니다.

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => resolve("completed!!"), 1000);
});

myPromise.finally(로딩 인디케이터 중지);

.then(f, f) 와 같지 않은 점은 다음과 같습니다.

  1. finally 핸들러에는 인수가 없습니다. 따라서, 이행되었는지 거부되었는지 알 수 없으므로 보편적 동작을 수행합니다.

  2. 자동으로 다음 핸들러에 결과와 에러를 전달합니다.

myPromise.finally(() => alert("promise ready"))
.then(result => alert(result));
  1. .finally(f) 는 함수 f 를 중복해서 쓸 필요가 없기 때문에 .then(f, f) 보다 문법 측면에서 더 편리합니다.

프로미스 체이닝

위와 같은 then, catch, finally 등을 활용하여 비동기 처리를 순서대로 해주는 것을 프로미스 체이닝 이라고 합니다.

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('completed');
    resolve();
  }, 1000);
});

myPromise
  .then(...)
  .then(...)
  .then(...);

then, catch, finally 는 언제나 프로미스를 반환하므로 연속적으로 호출할 수 있습니다. 프로미스는 프로미스 체이닝을 통해 비동기 처리 결과를 전달받아 후속처리를 하므로 콜백 지옥이 발생하지 않습니다.

하지만 프로미스도 콜백 패턴을 사용하므로 더욱 가독성이 좋게 작성하기 위해 도입된 것이 async/await 입니다.

async/await 와의 차이

async/await 과는 사용방식과 가독성에 차이가 있습니다.

프로미스는 비동기 작업의 최종완료, 실패를 나타내는 객체이며 여러개의 .then 절을 연결하여 비동기 작업을 순차적으로 처리할 수 있으나

async/await은 async 함수 내에서 await 키워드를 사용하면 해당 비동기 작업이 완료될 때까지 함수의 실행을 일시적으로 중단하며, try...catch 블록을 사용하여 에러 처리가 가능합니다.

profile
null 사랑하지 않아 - 어반자카파

0개의 댓글