티스토리 뷰

🍕Promise란? 

Promise는 프로미스가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자로, 비동기 연산이 종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있습니다. 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있습니다. 다만 최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 '약속'(프로미스)을 반환
ref : MDN 

-자바스크립트에서 제공하는 비동기를 간편하게 처리할 수 있도록 도와주는 내장 obj(객체) 이다. 

-비동기 적인 것을 수행할 때 콜백 함수 대신에 유용하게 사용할 수 있는 객체

-장시간의 기능을 수행하고 나서 정상적으로 기능이 수행됐다면 성공 메세지와 함께 처리된 결과값을 전달해주고 기능이 제대로 수행되지 못했다면 error를 전달해준다. 

 

Promise 는 executor라는 콜백함수를 받는데 executor 함수는 2개의 콜백함수를 인자로 받습니다. 바로 resolve와 reject 입니다. 기본적으로 promise는 함수에 콜백을 전달하는 대신에, 콜백을 첨부하는 방식의 객체이다. 

  • resolve : 함수 안의 처리가 성공적으로 끝났을 때 호출해야 하는 콜백 함수, resolve 함수에는 어떠한 값도 인수로 넘길 수 있다. 이 값은 다음 처리를 실행하는 함수에 전달된다. 
  • reject : 함수 안의 처리가 실패했을 때 호출해야하는 콜백함수, reject 함수에도 어떠한 값도 인수로 받을 수 있는데 주로 오류 메세지 문자열을 인수로 넘긴다. e.g. ) reject(new Error('request is rejected!'); 

🍕프로미스 키포인트 2가지

  1. Promise 객체의 state(상태)
  2. producer(정보 제공자)와 consumer(정보를 쓰는 소비자)의 관점 잘 이해하기 

1. Producer 제공자 

여기서 클래스라는 것은 생성자 함수를 말하는 거 같음

 
// 1.Producer (제공자)
// 프로미스는 클래스라서 new 키워드를 통해서 obj를 생성할 수 있음
// 유의 사항: 프로미스 객체가 만들어지면 내부 콜백함수인 executor가 자동적으로 실행된다.
const promise = new Promise((resolve, reject) => {
  // doing some heavy work()
  console.log('doing something...');
  setTimeout(() => {
    // 성공시 resolve라는 콜백함수에 사용자 이름 전달
    // resolve('cong');
    // 실패시 reject 함수사용 -> 보통 error라는 객체를 통해서 값을 전달
    reject(new Error('no network!'));
  }, 2000);
});
// 프로미스 객체 안에서 heavy한 일을 하게 되는데
// 네트워크에서 데이터를 받아오거나 파일에서 큰 데이터를 읽어오는 과정은
// 시간이 오래걸림 이런 일을 동기적으로 처리하면 다음 코드가 실행되지 않기 때문에
// 시간이 좀 걸리는 일들은 비동기적으로 처리하는 것이 좋다.
// -> 네트워크 통신, 파일읽기 등 비동기처리로 하는 것이 good

2. Consumner (사용자)

  • then(), catch(), finally() 
// 2. Comsumers (사용자): then, catch, finally 를 통해 값을 받아올 수 있음
// .then() 프로미스가 정상적으로 수행이 되어서 resolve라는 콜백함수를 통해서
// 전달한 값이 value의 param으로 들어와서 전달됨

// then을 호출하게 되면 promise객체를 리턴하기 때문에
// .catch를 또 사용할 수 있음(이걸 promise chaning이라고 함)
promise
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => console.log('finally'));
// .fianlly 는 성공하든 실패하든 상관없이 무조건 마지막에 호출되어지는 아이
// 즉, 성공 실패 여부 상관없이 어떤 기능을 마지막에 실행하고

 

 

프로미스의 3가지 상태(state) 

-상태란 ? 프로미스의 처리 과정을 의미 

-new Promise()로 프로미스를 생성하고 종료될 때까지 3가지 상태를 갖는다. 

  1. Pending(대기): 비동기 처리 로직이 아직 완료되지 않은 상태
  2. Fulfilled(이행): 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태 
  3. Rejected(실패): 비동기 처리가 실패하거나 오류가 발생한 상태 

🍔Pending(대기)

-new Promise(); 메서드를 호출하면 대기(Pending)상태가 된다. 

해당 메서드를 호출할 때 콜백함수를 선언할 수 있고, 콜백함수의 인자는 resolve, reject 이다. 

*위에서 executor라는 콜백함수가 2개의 인자를 받는다고 했던 것이 해당 부분, 표현을 executor이지 보통의 콜백함수는 익명함수로 쓰기 때문에 아래와 같이 표현됩니다. 

new Promise((resolve, reject)=>{
// ...
});

🍔Fufilled(이행)

여기서 콜백함수의 인자 resolve를 아래와 같이 실행하면 이행(fulfilled)상태가 된다. 

new Promise((resolve, reject)=>{
resolve();
});

그리고 이행 상태가 되면 아래와 같이 then()을 이용하여 처리 결과 값을 받을 수 있다.

//fulfilled(이행) 상태 -> 다른 말로하면 성공적으로 완료 상태
function getData() {
  return new Promise((resolve, reject) => {
    const data = 100;
    resolve(data);
  });
}

// resolve()의 결과 값 data를 resolvedData로 받음
getData().then((resolvedData) => {
  console.log(resolvedData); // 100
});

🍔Rejected(실패, 거부됨) 

reject를 아래와 같이 호출하면 실패(rejected)상태가 됩니다. 

// reject는 executor콜백함수의 인자이자 콜백함수라서 reject()이렇게 쓰는 걸로 앎
new Promise((resolve, reject) => {
  reject();
});

그리고, 실패 상태가 되면 실패한 이유(실패 처리의 결과 값)을 catch()로 받을 수 있다. 

function getData2() {
  return new Promise((resolve, reject) => {
    reject(new Error('Request is failed!'));
  });
}

//reject()의 결과 값 Error를 err에 받음
getData2() //
  .then()
  .catch(console.log);

ref: MDN

🍕프로미스의 에러 처리 방법

1. then()의 두번째 인자로 에러를 처리하는 방법 

buySomething().then(
    handleSuccess, 
    handleError
);

2.catch()를 이용하는 방법 ✔ (recommended)

buySomthing().then().catch();

 

-> 두 방법 모두 프로미스의 reject()메서드가 호출되어 실패 상태가 된 경우에 실행된다. 프로미스의 로직이 정상적으로 돌아가지 않는 경우 호출되는 것

결론: 더 많은 예외 처리 상황을 위해 프로미스의 끝에 가급적 then()의 두번째 인자로 error를 잡는 것보단 catch() 메소드를 사용하는 것이 좋다. 

 

🍕then의 2번째 인수(arg)로 에러처리하기

*인수(argument), 인자(parameter, 매개변수)

//reject 예제

let 프로미스2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    let num = parseInt(prompt('10 이하의 숫자를 입력하세요'));
    if (num < 10) {
      resolve('정답');
    } else {
      reject(new Error(`오류 : 숫자 ${num}은 10을 초과했습니다.`));
    }
  }, 1000);
});
// 프로미스2 //
//   .then(console.log)
//   .catch(console.log);

//then() 메서드의 두번째 인수
// then 메서드의 두번째 인수로 실패 콜백 함수를 지정하면 catch 메서드를 사용하지 않고 하나로 작성할 수 있다.
프로미스2.then(
  (num) => {
    console.log(`정답 : 숫자 ${num}은 10이하 입니다.`);
  },
  (error) => {
    console.log(error);
  },
);
  • then 메서드의 두번째 인수로 실패 콜백 함수를 지정하면 catch 메서드를 사용하지 않고 하나로 작성할 수 있다. 
  • 기존에 .catch(console.log) 였던 것을 then 의 두번째 인수로 (error) => {console.log(error);}, 이렇게 주는것 인수로 주는 것이므로 당연히 , 콤마로 구분해줘야 한다.

 

🍕프로미스 에러 처리는 가급적 catch()를 사용하기

function getData() {
  return new Promise((resolve, reject) => {
    resolve('hi it worked !');
  });
}

getData().then(
  (result) => {
    console.log(result);
    throw new Error('Error in then()');
  },
  (err) => {
    console.log('then error : ', err);
  },
);

getData() 함수의 프로미스에서 resolve()메서드를 호출하여 정상적으로 로직을 처리했지만, then()의 첫번째 콜백함수 내부에서 오류가 나는 경우 오류를 제대로 잡지 못한다. 따라서 코드를 실행하면 에러가 난다.

then의 2번째 인수로 error 처리한 결과 에러가 안잡힌다.

getData() //
  .then((result) => {
    console.log(result); // hi
    throw new Error('Error in then()');
  })
  .catch((err) => console.log('then error: ', err));
//결과: then error:  Error: Error in then()

-> 위처럼 catch()메소드를 사용하면 then error : 첫번째 콜백함수의 에러 메세지(new Error)가 제대로 출력되면서 에러가 잡힌다. 


▶Promise가 실행하는 콜백함수에 인수 넘기기
  • Promise가 실행하는 콜백함수에 budget이라는 인수를 넘겨 사용하도록 처리
  • 아래의 예시에서는 buySomething()에 budget으로 2000을 전달
// Promise가 실행하는 콜백함수에 인수를 넘길 수도 있다. 그러기 위해선 Promise 객체를 반환하는
// 함수를 정의하여 그 함수의 인수에 원하는 값을 넘기면 된다.

//예제: 비동기 처리로 상품에 대한 지불 금액을 입력한 후에 잔액을 표시하는 프로그램

function buySomething(budget) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let payment = parseInt(prompt('얼마를 지불하시겠습니까?'));
      let balance = budget - payment;
      if (balance >= 0) {
        console.log(`${payment}원을 지불하셨습니다.`);
        resolve(balance);
      } else {
        reject(`잔액이 부족합니다. 잔액은 ${budget}원 입니다.`);
      }
    }, 1000);
  });
}
buySomething(2000) //
  .then((balance) => {
    console.log(`남은 예산은 ${balance}원 입니다.`);
  })
  .catch(console.log);
비동기 처리 여러 개를 직렬로 연결하기 
  • 비동기 처리 여러개를 직렬로 연결(순차적인 실행)
  • Promise를 사용하면 비동기 처리를 여러 개 연결하여 순차적으로 실행할 수 있다. then 메서드 안에서 실행하는 성공 콜백함수가 다시 Promise객체를 반환하도록 코드를 처리하면 된다. 
buySomething(2000) //
  .then((balance) => {
    return buySomething(balance);
  })
  .then((balance) => {
    return buySomething(balance);
  })
  .catch(console.log);
▶비동기 처리 여러 개를 병렬로 연결하기 
  • Promise.all() 메서드 사용
  • 사용법: Promise.all(iterable); // * iterable은 반복가능한 객체를 말한다.
  • Promise 객체가 요소로 들어있는 배열을 넘기면 Promise.all 메서드는 그 안의 요소로 들어있는 모든 Promise 객체를 병렬로 실행한다. 해당 객체에서 resolve함수를 호출 한 후 then 메서드에 지정한 함수(다음에 처리해야할 작업)를 실행한다. 각각의 resolve 함수의 인수가 response라는 배열의 요소로 들어가 반환된다. 
  • 해당 Promise 객체에서 하나라도 실패로 끝난다면 가장 먼저 실패로 끝난 Promise 객체에서 실행한 reject함수의 인수를 실패 콜백함수의 인수로 받을 수 있다. 
  • Promise 객체의 all 메서드를 사용하면 비동기 처리 여러 개를 병렬로 실행할 수 있다. 그리고 모든 처리가 성공했을 때만 다음 작업을 실행하도록 처리할 수 있다
function buySomething(name, budget) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let payment = parseInt(prompt(`${name}님, 얼마를 지불하시겠습니까?`));
      let balance = budget - payment;
      if (balance >= 0) {
        console.log(
          `${payment}원을 지불하셨습니다. ${name}님의 남은 예산은 ${balance}원 입니다.`,
        );
        resolve(balance);
      } else {
        reject(`잔액이 부족합니다. ${name}님의 잔액은 ${budget}원 입니다.`);
      }
    }, 1000);
  });
}
// 비동기 처리 병렬로 연결 예제
Promise.all([
  buySomething('김감자', 1000),
  buySomething('황구링', 2000),
  buySomething('파이리', 500),
]) //
  .then((balance) => {
    console.log(balance);
  })
  .catch(console.log);
  
  /*성공시 결과: 
200원을 지불하셨습니다. 김감자님의 남은 예산은 800원 입니다.
100원을 지불하셨습니다. 황구링님의 남은 예산은 1900원 입니다.
200원을 지불하셨습니다. 파이리님의 남은 예산은 300원 입니다.
Array(3)
0: 800
1: 1900
2: 300
*/

성공시 결과

성공했을 때 결과값으로 all 메서드의 인수로 받은 배열의 요소로 등록된 모든 Promise객체가 실행된다. 

모든 작업이 성공으로 끝나면 then 메서드에 등록한 성공 콜백함수가 실행된다. 그리고 Promise 객체에서 실행된 resolve함수의 인수가 response라는 배열의 요소 값으로 들어가 반환된다(response 배열의 값으로 들어간 값들이 resolve 함수의 인수임 예시에서는 balance 즉, 잔액이겠네요). 

-> 간단히 말하면 각각의 Promise마다 성공 콜백함수가 실행되고 resolve로부터 전달받은 값을 담은 배열이 리턴됨

 

실패시 결과

하나라도 실패하면 catch메서드에 등록한 실패 콜백함수가 실행되고 가장 먼저 실패로 끝난 Promise객체에서 실행한 reject 함수의 인수가 콜백함수의 인수로 들어간다. 여기서는 `잔액이 부족합니다 ~ 부분` 

-> 하지만 병렬 처리이기 때문에 하나가 성공했을 때는 해당 성공한 것과는 별개로 가장 먼저 실패한 것을 catch 메서드로 처리한다. (= 반환하는 프로미스가 거부된다면, 매개변수의 프로미스 중 거부된 첫 프로미스의 사유를 그대로 사용합니다.)

 

 

Promise.race() 메소드

- Promise.race 메서드는 경주(race)에서 승리한 작업, 즉 가장 먼저 종료한 Promise 객체의 결과만 다음 작업으로 보내는 처리 방법이다. 즉, 먼저 종료한 작업이 성공했을 때는 성공 콜백을 호출하고 실패했을 때는 실패 콜백을 호출한다. 나머지 Promise객체도 실행되기는 하지만 반환하는 것은 먼저 종료한 작업의 결과값이다

사용법: Promise.race(iterable)

Promise.race([
  buySomething('김감자', 1000),
  buySomething('황구링', 2000),
  buySomething('파이리', 500),
]) //
  .then((balance) => {
    console.log(balance);
  })
  .catch(console.log);
/* 결과 
200원을 지불하셨습니다. 김감자님의 남은 예산은 800원 입니다.
800
1000원을 지불하셨습니다. 황구링님의 남은 예산은 1000원 입니다.
30원을 지불하셨습니다. 파이리님의 남은 예산은 470원 입니다.
*/
/* 위에서 Promise.all과 다르게 Promise.race는 프로미스 중에 가장 먼저 완료된 것의 
결과 값으로 그대로 이행하거나 거부한다.
그래서 김감자의 프로미스 rosolve의 결과값을 받아서 이행했음*/

All ref: 

https://joshua1988.github.io/web-development/javascript/promise-for-beginners/

 

자바스크립트 Promise 쉽게 이해하기

(중급) 자바스크립트 입문자를 위한 Promise 설명. 쉽게 알아보는 자바스크립트 Promise 개념, 사용법, 예제 코드. 예제로 알아보는 then(), catch() 활용법

joshua1988.github.io

https://tangoo91.tistory.com/42

 

[JavaScript] 자바스크립트 비동기 처리와 AJAX, promise(프로미스)

동기와 비동기 처리 동기와 비동기 처리 동기 처리(Synchronous Processing) : 하나의 작업이 끝난 뒤 다음 작업을 진행하는 순차적인 처리 방식 비동기 처리(Asynchronous Processing) : 하나의 작업의 종료까

tangoo91.tistory.com

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

 

Promise - JavaScript | MDN

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

developer.mozilla.org

 

댓글