본문 바로가기

프로그래밍/JavaScript(JS)

자바스크립트(JavaScript) - Promise

글에 오류가 있을 수 있으니 있다면 알려주시기 바랍니다!

 

비동기 작업을 할 때 콜백을 사용합니다. 실행 순서가 보장받아야 할 때는 콜백을 중첩하여 사용하기도 합니다.

 

콜백을 중첩해서 사용할때 보기가 힘들고 이해가 어려울 때가 있습니다. 이 문제를 해결할 수 있는 방법이 Promise입니다.

 

Promise는 비동기 작업에서의 콜백을 좀 더 간결하게 작성할 수 있게 해 주며 실행 순서를 보장받을 수 있도록 할 수도 있습니다.

프로미스(Promise)

Promise는 peding, fulfilled, rejected 3개의 상태로 구분됩니다.

  • peding 상태는 아직 Promise 작업이 끝나지 않은 상태
  • fulfilled 상태는 Promise 작업이 끝낸 상태
  • rejected 상태는 Promise 작업 중 에러가 발생한 상태

Promise의 상태는 resolve, reject 함수를 호출되면 상태가 변경됩니다.

 

Promise를 사용하는 방법은 Promise 객체를 생성하여 실행할 수 있습니다.

var a = new Promise(function(resolve,reject) {
  // 비동기 작업
  setTimeout(function() {
    console.log("A");
    resolve();
  },1000);
});
a.then(function() {
  console.log("작업 완료!");
})

// -> A
// -> 작업 완료!

Promise 객체는 작업을 하는 함수를 첫 번째 인수로 주고 함수의 인자로는 resolve, reject가 주어집니다. 

 

resolve는 작업이 완료되었을 때 호출되는 함수, reject은 작업에 실패하였을 때 호출되는 함수입니다.

 

해당 코드가 실행되면 바로 Promise의 주어진 작업이 실행되고 1초가 되면 A가 출력되고 resolve 함수를 호출합니다.

 

resolve 함수가 호출되면 then 메서드에 주었던 함수가 호출됩니다. then 메서드는 새로운 promise 객체를 리턴합니다.

 

then 메서드에 주는 함수에 인자가 있다면 resolve 함수를 호출할 때 인수로 넘겨진 값을 사용할 수 있습니다.

var a = new Promise(function(resolve,reject) {
  // 비동기 작업
  setTimeout(function() {
    console.log("A");
    resolve("A");
  },1000);
});
a.then(function(result) {
  console.log(`${result} 작업 완료!`);
});

// -> A
// -> A 작업 완료!

Promise 에러 처리

Promise에서 에러 처리는 then 메서드 두 번째 인수에 함수를 추가하거나 catch 메서드를 사용합니다.

 

then 두 번째 인자는 reject 함수가 호출될때 호출되는 함수입니다.

 

then 두번째 인수를 사용하여 에러 처리는 reject 함수가 호출되는 것으로만 에러를 처리할 수 있고 다른 문제로 에러가 발생하면 처리가 불가능합니다.

 

따라서 catch 메서드를 사용하여 주어진 함수를 호출하여 처리합니다.

 

reject 함수 말고 직접 throw로 에러를 발생시키거나 다른 이유로 에러가 발생돼도 catch 메서드에 주어진 함수가 호출됩니다.

 

catch 메서드가 없다면 throw 에러나 다른 이유로 발생하는 에러가 발생해도 처리가 불가능합니다.

 

reject 함수도 resolve와 동일하게 사용법이 같습니다.

 

catch 메서드는 reject 함수가 호출되면 catch 메서드에 있는 함수가 호출되는 함수를 지정하며 프로미스가 반환됩니다.

 

reject 함수도 resolve와 동일하게 인수로 값을 넘길 수 있습니다. 해당 값은 catch 메서드에 지정된 함수의 인수값으로 사용됩니다.

var a = new Promise(function(resolve,reject) {
  // 비동기 작업
  setTimeout(function() {
    let value = true;
    if(value == false) {
      console.log("A");
      resolve("A");
    }
    else {
      reject("false 입니다.");
    }
  },1000);
  });
  a.then(function(result) {
    console.log(`${result} 작업 완료!`);
  })
  .catch(function(error) {
    console.log(`에러> ${error}`);
});

// -> 에러> false 입니다.

var a = new Promise(function(resolve,reject) {
  // 비동기 작업
  setTimeout(function() {
    let value = true;
    if(value == true) {
      resolve("A");
    }
    else {
      reject("false 입니다.");
    }
  },1000);
});
a.then(function(result) {
  return new Promise(function(resolve,reject) {
    var arr = ["a","b","c"];
    console.log(arr[4].toString());
    resolve(`${result} B`);
  });
})
.then(function(result) {
  console.log(result);
})
.catch(function(error) {
  console.log(`${error}`); // -> TypeError: Cannot read properties of undefined (reading 'toString')
});

프로미스 체인(Promise Chain)

프로미스 객체에서 then, catch 메서드를 사용하여 여러 비동기 작업을 순서대로 실행이 가능합니다.

 

then, catch 메서드는 반환 값으로 새로운 Promise 객체를 반환합니다.

 

따라서 반환된 Promise 객체에 있는 then 메서드를 이용해 새로운 프로미스를 만들어 비동기 작업을 할 수 있습니다.

 

이렇게 프로미스 객체를 이용해서 새로운 비동기 작업을 이어나가는 것을 Promise Chain이라고 합니다.

var a = new Promise(function(resolve,reject) {
  // 비동기 작업
  setTimeout(function() {
  	resolve("A");
  },1000);
});
a.then(function(result) {
  return new Promise(function(resolve,reject) {
    resolve(`${result} B`);
  });
})
.then(function(result) {
  return new Promise(function(resolve,reject) {
    resolve(`${result} C`);
  });
})
.then(function(result) {
  console.log(result);
});

// -> A B C

a 프로미스로 시작으로 then 메서드를 통해 새로운 프로미스가 만들어지고 만들어진 프로미스의 then 메서드로 새로운 프로미스가 만들어지면서 이어나가게 되고 마지막 then 메서드에 지정된 함수에 따라 결과를 출력하게 됩니다.

 

이렇게 해서 기존에 콜백을 중첩으로 사용하는 것과 같은 결과가 나오게 되지만 보기에는 편한 코드가 되었습니다.

 

어떻게 익명 함수에서 새로 만든 Promise 객체를 리턴을 하는데 then 메서드가 반환하는 Promise 객체에 값이 변경되는가?

내부적으로 then 메서드는 Promise가 pending 상태일 때 Promise 객체에 then 메서드에 지정한 함수를 저장해둡니다.

 

저장하는 정보로는 then에서 만든 새로운 Promise, then에서 지정한 함수를 저장됩니다.

 

그 후 resolve 함수가 호출되면 then에서 저장한 함수를 Promise 객체에서 가져와 호출합니다. 

 

따라서 a 프로미스가 작업이 끝나면 a Promise 객체에 저장된 함수가 호출되어 새로운 Promise 객체(익명 함수가 리턴한 객체)를 만들게됩니다. 동시에 resolve 함수가 호출됩니다.

 

이렇게 만들어진 익명함수가 리턴한 Promise 객체에 결과는 A B로 저장된 Promise 객체가 됩니다.

 

해당 객체를 이용해 다시 a Promise 객체의 resolve 함수를 호출하여 then에서 리턴한 Promise 객체에 결과를 넣게 됩니다.

 

간단하게 썼지만 자세하게 보고 싶다면 EcmaScript 2015 언어 스펙 문서를 확인하시기 바랍니다. 최신 문서를 보셔도 됩니다.

 

Promise.all 메서드와 race 메서드

지금 까지는 Promise를 이용해 순서대로 호출되는것을 알아보았습니다.

 

다음으로는 all 메서드와 race 메서드를 간단히 알아보겠습니다.

 

all 메서드는 모든 작업이 모두 끝났을 경우 then에 지정된 함수가 호출되며 값으로는 배열로 resolve 함수 인수로 넘긴 값이 들어갑니다.

Promise.all([
  new Promise(function(resolve,reject) {
    resolve(`A`);
  }),
  new Promise(function(resolve,reject) {
    resolve(`B`);
  }),
  new Promise(function(resolve,reject) {
    resolve(`C`);
  })
])
.then(function(result) {
  console.log(result); // -> ['A', 'B', 'C']
})
.catch(function(error) {
  console.log(`${error}`);
});

하지만 작업 도중 reject 함수가 호출되면 마지막으로 resolve 함수에 인수로 넘긴 값이 결과가 됩니다.

 

reject 함수가 아닌 에러가 발생하면 발생한 에러가 마지막 결과값이 됩니다.

Promise.all([
  new Promise(function(resolve,reject) {
    resolve(`A`);
  }),
  new Promise(function(resolve,reject) {
    reject(`B`);
  }),
  new Promise(function(resolve,reject) {
    resolve(`C`);
  })
])
.then(function(result) {
  console.log(result);
})
.catch(function(error) {
  console.log(`${error}`); // -> B
});


Promise.all([
  new Promise(function(resolve,reject) {
    resolve(`A`);
  }),
  new Promise(function(resolve,reject) {
    throw new Error("error");
    resolve(`B`);
  }),
  new Promise(function(resolve,reject) {
    resolve(`C`);
  })
])
.then(function(result) {
  console.log(result);
})
.catch(function(error) {
  console.log(`${error}`); // -> error
});

다음으로 race 메서드를 알아 보겠습니다. race 메서드는 비동기 작업중 여러 작업을 실행하며 제일 빨리 작업을 종료한 객체의 결과를 then에 인수로 넘긴 함수의 인수로 넘겨지며 호출됩니다.

Promise.race([
  new Promise(function(resolve,reject) {
    setTimeout(() => {
      resolve("A");
    }, 3000);
  }),
  new Promise(function(resolve,reject) {
    setTimeout(() => {
      resolve("B");
    }, 5000);
  }),
  new Promise(function(resolve,reject) {
    setTimeout(() => {
      resolve("C");
    }, 500);
  })
])
.then(function(result) {
  console.log(result); // -> C
})
.catch(function(error) {
  console.log(`${error}`);
});

 

참고

모던 자바스크립트 입문(이소 히로시 지음, 서재원 옮김)

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

 

Promise - JavaScript | MDN

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

developer.mozilla.org