Promise

100pearlcent·2021년 11월 7일
0

JavaScript

목록 보기
20/22
post-thumbnail
  • 제작 코드 (producing code) : 원격에서 스크립트 불러오기 같은 시간이 걸리는 일

  • 소비 코드 (consuming code) : 제작 코드의 결과를 기다렸다가 이를 소비한다. 소비의 주체(함수)는 여럿이 될 수 있다.

  • promise : 제작코드와 소비코드를 연결해 주는 자바스크립트 객체. 시간이 얼마가 걸리든 상관없이 약속한 결과를 만들어 내는 제작 코드가 준비되었을 때, 모든 소비 코드가 결과를 사용할 수 있도록 한다.


// Promise 객체 

let promise = new Promise(function(resolve, reject) {
  // executor
});

new Promise에 전달되는 함수는 executor(실행자, 실행함수)라고 부른다
executor는 new Promise가 만들어질 때 자동으로 실행되는데, 결과를 최종적으로 만들어내는 제작 코드를 포함한다

executor의 인수 resolvereject는 자바스크립트에서 자체 제공하는 콜백이다
개발자는 resolvereject를 신경쓰지 않고 executor 내 코드만 작성하면 된다

⚠️ 대신 executor에선 결과를 즉시 얻든 늦게 얻든 상관없이 상황에 따라 인수로 넘겨준 콜백 중 하나를 반드시 호출해야 한다


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

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

👉 executor는 자동으로 실행되는데 여기서 원하는 일이 처리된다. 처리가 끝나면 executor는 처리 성공 여부에 따라 resolvereject를 호출한다



한편, new Promise 생성자가 반환하는 promise객체는 다음과 같은 내부 프로퍼티를 갖는다

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

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


let promise = new Promise(function(resolve, reject) {
  // 프라미스가 만들어지면 executor 함수는 자동으로 실행

  // 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되고
  // result는 'done'이 된다
  setTimeout(() => resolve("done"), 1000);
});

setTimeout을 통해 executor 함수는 약간의 시간이 걸리도록 구현했다

executor '처리'가 시작되고 1초 후에 resolve("done")이 호출되고 결과가 만들어진다
이 때 promise 객체의 state"pending"에서 "fulfilled"가 되고, resultundefined에서 "done"이 된다

이처럼 일이 성공적으로 처리되었을 때의 프라미스를 fulfilled promise라고 부른다



let promise = new Promise(function(resolve, reject) {
  // 1초 뒤에 에러와 함께 실행이 종료되었다는 신호를 보냄
  setTimeout(() => reject(new Error("에러 발생")), 1000);
});

executor가 에러와 함께 약속한 작업을 거부하는 경우에는 1초 후 reject(...)가 호출되면 promise의 상태가 "rejected"로 변한다



프라미스는 성공이나 실패만 한다

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); // 무시됨
  setTimeout(() => resolve("…")); // 무시됨
});

executor는 resolvereject 중 하나를 반드시 호출해야 한다
이 때 변경된 상태는 더 이상 변하지 않는다
처리가 끝난 프라미스에 resolvereject를 호출하면 무시되므로 executor에 의해 처리가 끝난 일은 결과 혹은 에러만 가질 수 있다

  • resolvereject는 인수 하나만을 받거나 아예 받지 않고 그 이외의 인수는 무시한다는 특성이 있다



Error 객체와 함께 거부하기

무언가 잘못된 경우, executor는 reject를 호출해야 한다
이 때 인수는 resolve와 마찬가지로 어떤 타입도 가능하지만 Error객체 또는 Error를 상속받은 객체를 사용하는 편이 좋다



resolve, reject 함수 즉시 호출하기

executor는 대개 무언가를 비동기적으로 수행하고, 약간의 시간이 지난 후에 resolve, reject를 호출하는데 꼭 이렇게 할 필요는 없다

let promise = new Promise(function(resolve, reject) {
  // 일을 끝마치는 데 시간이 들지 않음
  resolve(123); // 결과(123)를 즉시 resolve에 전달함
});

어떤 일을 시작했는데 알고보니 이미 일이 끝나 저장까지 되어있는 경우 이렇게 resolvereject를 즉시 호출하는 방식을 사용할 수 있다
이렇게 하면 프라미스는 즉시 이행 상태가 된다



state 와 result는 내부에 있다

프라미스 객체의 state, result 프로퍼티는 내부 프로퍼티이므로 개발자가 직접 접근할 수 없다
.then/.catch/.finally메서드를 사용하면 접근 가능하다




소비자 then, catch, finally

프라미스 객체는 executor의 결과나 에러를 받을 소비 함수를 이어주는 역할을 한다
소비함수는 .then, .catch, .finally 메서드를 사용해 등록된다

- ✨ then

프라미스에서 가장 중요하고 기본이 되는 메서드

promise.then(
  function(result) { /* 결과(result)를 다룸 */ },
  function(error) { /* 에러(error)를 다룸 */ }
);

.then의 첫 번째 인수는 프라미스가 이행되었을 때 실행되는 함수이고, 여기서 실행결과를 받는다
.then의 두 번째 인수는 프라미스가 거부되었을 때 실행되는 함수이고 에러를 처리한다


let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

// resolve 함수는 .then의 첫 번째 함수(인수)를 실행
promise.then(
  result => alert(result), // 1초 후 "done!"을 출력
  error => alert(error) // 실행되지 않음
);

만약 작업이 성공적으로 처리된 경우만 다루고 싶다면 아래 예시처럼 .then에 인수를 하나만 전달하면 된다

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); // 1초 뒤 "done!" 출력



- ✨ catch

.catch(f)는 문법이 간결하다는 점만 빼고 .then(null, f)와 같다

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

promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력

에러가 발생한 경우만 다루고 싶다면 .then(null, errorHandlingFunction)과 같이 null을 첫 번째 인수로 전달하면 된다
.catch(errorHandlingFunction)을 써도 되는데 이는 .catch.thennull을 전달하는 것과 동일하게 작동하기 때문이다



- ✨ finally

프라미스는 처리(이행 혹은 거부)되면 f가 항상 실행된다는 점에서 .finally(f)호출은 .then(f, f)와 유사하다
쓸모가 없어진 로딩 인디케이터를 멈추듯 결과가 어떻든 마무리가 필요할 때 finally는 유용하다

new Promise((resolve, reject) => {
  /* 시간이 걸리는 어떤 일을 수행하고
  그 후 resolve, reject를 호출함 */
})
  // 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨
  .finally(() => 로딩 인디케이터 중지)
  .then(result => result와 err 보여줌 => error 보여줌)

finally와 .then(f, f)가 다른점

  1. finally 핸들러엔 인수가 없다. finally에선 프라미스가 이행되었는지, 거부되었는지 알 수 없고 절차를 마무리하는 동작을 수행하므로 성공 실패 여부를 몰라도 된다
  2. finally 핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달한다
new Promise((resolve, reject) => {
  setTimeout(() => resolve("결과"), 2000)
})
  .finally(() => alert("프라미스가 준비되었습니다."))
  .then(result => alert(result)); // <-- .then에서 result를 다룰 수 있음

result가 finally를 거쳐 then까지 전달되는 것을 확인 할 수 있다
finally는 프라미스 결과를 처리하기 위해 만들어진게 아니다
3. .finally(f)는 함수 f를 중복해서 쓸 필요가 없기 때문에 .then(f, f)보다 문법 측면에서 더 편리하다



요약

  • executor는 보통 시간이 걸리는 일을 수행하고, 일이 끝나면 resolvereject함수를 호출하는데 이 때 프라미스 객체의 상태가 변화한다
  • resolved 혹은 rejected 상태의 프라미스는 settled 프라미스라고 부른다 이와 반대되는 pending 프라미스도 있다

0개의 댓글