티스토리 뷰

11.2 Promise

  1. 제작 코드(producing code)'는 원격에서 스크립트를 불러오는 것 같은 시간이 걸리는 일을 한다.
  2. '소비 코드(consuming code)'는 '제작 코드’의 결과를 기다렸다가 이를 소비한다. 소비 주체(함수)는 여럿이 될 수 있다.
  3. 프라미스(promise) 는 '제작 코드’와 '소비 코드’를 연결해 주는 특별한 자바스크립트 객체다. '프라미스’는 시간이 얼마나 걸리든 상관없이 약속한 결과를 만들어 내는 '제작 코드’가 준비되었을 때, 모든 소비 코드가 결과를 사용할 수 있도록 해준다.

promise는 아래 예시처럼 만들 수 있다. 전달되는 함수는 executor 함수라고 부른다. 

let promise = new Promise(function(resolve, reject) {
  // executor (제작 코드, '가수')
});

executor는 promise가 만들어질 때 자동으로 실행되고, 결과를 최종적으로 만드는 제작코드를 포함한다. resolve, reject는 자체적으로 제공되는 콜백이다.

 

executor에서는 결과를 언제 얻든지 간에 상황에 따라 인수로 넘겨준 콜백 중 하나를 반드시 호출해야 한다. 

  • resolve(value) - 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출
  • reject(error) - 에러 발생 시 에러 객체를 나타내는 error와 함께 호출

 

new Promise 생성자가 반환하는 객체는 아래 속성을 가진다.

  • state — 처음엔 "pending"(보류)이었다 resolve가 호출되면 "fulfilled", reject가 호출되면 "rejected"로 변합니다.
  • result — 처음엔 undefined이었다 resolve(value)가 호출되면 value로, reject(error)가 호출되면 error로 변합니다.

결국 executor의 결과에 따라 promise 객체의 상태가 변화되는 것이다.

 

성공과 실패 예시

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

  // 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되면서 result는 '완료'가 됩니다.
  setTimeout(() => resolve("완료"), 1000);
});

// -----------

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

프라미스는 성공 아니면 실패만 한다. 이미 처리된 프라미스에 resolve와 reject를 호출하면 무시된다.

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

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

state, result 속성은 내부 속성이라서 개발자가 직접 접근하기 어렵다.

Consumers: then, catch, finally

세가지 함수를 사용하여 소비 동작을 수행할 수 있다.

then

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

첫번째 파라미터는 프라미스가 정상 동작했을 때 실행, 두번째는 거부되었을 때 실행되는 함수이다.

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

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

// ---------------

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

// reject 함수는 .then의 두 번째 함수를 실행합니다.
promise.then(
  result => alert(result), // 실행되지 않음
  error => alert(error) // 1초 후 "Error: 에러 발생!"을 출력
);

catch

에러가 발생한 경우만 다루고 싶을 때, catch를 사용한다. 

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

// .catch(f)는 promise.then(null, f)과 동일하게 작동합니다
promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력

then(null, errorFunction)로도 가능하다. catch(errorFunction)과 동일하게 작동한다.

finally

try...catch와 같이 finally가 존재한다. 

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

finally는 프라미스 이행, 거부 여부를 모른다. 그저 보편적인 동작만 수행하도록 설계되어 있다. 또한, 자동으로 바로 다음 핸들러로 결과와 에러를 전달한다.

new Promise((resolve, reject) => {
  setTimeout(() => resolve("결과"), 2000)
})
  .finally(() => alert("프라미스가 준비되었습니다."))
  .then(result => alert(result)); // <-- .then에서 result를 다룰 수 있음

finally인데 마지막에 사용되지 않는게 의아하다. 이는 결과를 처리하기 위해 만들어진게 아니기 때문이다. 체이닝에서 중요하게 다뤄질 것 같다.

Example: loadScript

function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error(`${src}를 불러오는 도중에 에러가 발생함`));

    document.head.append(script);
  });
}


// ---------------

let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");

promise.then(
  script => alert(`${script.src}을 불러왔습니다!`),
  error => alert(`Error: ${error.message}`)
);

promise.then(script => alert('또다른 핸들러...'));

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday