JavaScript 프로미스

1. Promise

비동기를 간편하게 처리할 수 있도록 도와주는 오브젝트이다.

  • 프로미스는 자바스크립트 안에 내장되어 있는 오브젝트이며, 비동기적인 것을 수행할 때 콜백함수 대신 유용하게 사용할 수 있는 오브젝트이다.

  • 정해진 장시간의 기능을 수행한 뒤, 정상적으로 기능이 수행되었다면 성공메세지와 함께 처리된 결과값을 전달하고, 기능을 수행하다가 문제가 발생한다면 에러를 전달해준다.

  • 네트워크통신이나 파일을 읽어 오는 것 등 어떤 무거운 일을 처리할 때 사용 될 수 있다.
    → 네트워크에서 데이터를 받아오거나 파일에서 큰 데이터를 읽어오는 과정은 다소 시간이 소요된다. 이를 만약 동기적으로 수행한다면 다음 라인의 코드가 실행되지 않기 때문에, 프로미스를 통해 비동기적으로 처리하는 것이 좋다.




📌 Promise 포인트 2가지

(1) state(상태)

프로세스가 무거운 작업을 수행하고 있는 중인지, 혹은 기능 수행을 완료(성공or실패) 했는지에 대한 상태(state)를 이해한다.

  • promise는 3가지 상태를 가진다.
    pending : 대기 상태로서 아직 resolve 할지 reject 할 지 결정되지 않은 초기의 상태이다.
    fulfilled : 이행 상태로써 연산이 성공적으로 완료된 상태이다.
    rejected state : 거부 상태로써 (파일을 찾을 수 없거나 네트워크에 문제가 생긴 이유 등) 실패된 상태이다.

✍️ pending 상태 예시 ▼

function fetchUser() {
    return new Promise((resolve, reject) => {
     return ('seul');
    })
}

const user = fetchUser();
console.log(user);
//resolve나 reject를 호출하지 않았기 때문

✍️ fulfilled 상태 예시 ▼

function fetchUser() {
    return new Promise((resolve, reject) => {
     resolve('seul');
    })
}

const user = fetchUser();
console.log(user);


(2) producer & consumer

producer와 consumer의 차이에 대해 이해한다.

  • producer
    : 원하는 데이터를 제공한다.

  • consumer
    : 제공된 데이터를 필요로 한다. 즉 데이터를 소비하는 측이다.




2. then & catch & finally

then()

  • then() 는 Promise를 반환하고 두 개의 콜백 함수를 인수로 받는다. (이행했을 때와 거부했을 때를 위한 콜백 함수)
  • promise 가 종료가 되면 resolve 에 들어간 값을 받을 수 있다.
  • reject 된 경우에 then으로 받으면 에러가 발생한다.
// ex.1) resolve와 then
const promise = new Promise((resolve, reject)=>{
  setTimeout(()=>{
    resolve("resolve");
  }, 3000);
});

promise.then(value => console.log(value));
// 3초 후에 결과가 출력
// resolve
// ex.2) reject와 then
const promise = new Promise((resolve, reject)=>{
  setTimeout(()=>{
    reject("reject");
  }, 3000);
});

promise.then(value => console.log(value));
// UnhandledPromiseRejectionWarning: reject

catch()

  • catch() 는 Promise를 반환하고 거부(실패)된 경우만 처리된다.
  • 위의 ex.2) 의 경우, catch를 사용해서 에러를 잡아줄 수 있다.
  • ex.3) catch가 then 뒤에 연결되어 있다고 해서 then이 실행된 후 catch가 실행되는 것은 아니다.
    → 둘은 같이 실행되지 않으며 resolve 시에는 then이, reject 시에는 catch가 실행되는 것이다.
// ex.3) reject와 catch
const promise = new Promise((resolve, reject)=>{
  setTimeout(()=>{
    reject("reject");
  }, 3000);
});

promise.then(result => console.log(result)).catch(error => console.log(error));
// 3초 후에 결과가 출력
// reject

then() & catch()

  • thencatch 메서드는 프로미스를 반환하기 때문에, 체이닝이 가능하다.

finally()

  • finally() 는 Promise를 반환하고 성공 실패와 상관없이 마지막에 무조건 호출된다.




3. Promise 생성 & 활용

PromiseConstructor

  • 프로미스는 클래스이기때문에 new 라는 키워드를 이용해서 오브젝트를 생성할 수 있다.
  • Promise는 매개변수로 executor를 받고, executor는 resolve와 reject 인수를 전달할 실행함수이다.
    → 즉, 프로미스의 생성자를 보면 executor 라는 콜백함수를 전달해 주어야 한다. 이 콜백함수에는 또 다른 두가지 콜백함수를 받는다. ▼
    (1) resolve : 기능을 정상적으로 수행해서 마지막에 최종 데이터를 전달한다.
    (2) reject : 기능을 수행하다가 중간에 문제가 생기면 호출한다.



3-1. Promise 만들기

✍️ 예제를 작성해보자.

const promise = new Promise((resolve, reject) => {
   console.log('doing something...'); // 즉시 실행된다.
});

// result ▼
// doing something

▲ ❗️ new Promise 가 만들어 질 때는, 전달한 executor 라는 콜백 함수가 즉시 실행된다.

  • 위의 console.log 가 즉시 실행된다는 것은 프로미스를 만드는 순간 전달한 executor라는 콜백함수가 바로 실행된다는 의미이다.
  • 즉, 만약 사용자가 버튼을 눌렀을 때 비로소 네트워크 요청 등을 필요로 한다면, 불필요한 네트워크 통신이 이루어질 수도 있다는 점을 주의해야 한다.

(1) resolve & reject

// Producer
const promise = new Promise((resolve, reject) => {
   console.log('doing something...'); 
   setTimeout(() => {
       resolve('seul');
   }, 2000)
});


// Consumers
promise.then((value) => {
    console.log(value);
});

// result ▼
// 'doing something...'
// (2초뒤) seul

then 은 프로미스가 정상적으로 수행되어서 resolve 라는 콜백함수를 통해서 전달한 값이 value 의 파라미터로 전달되어 들어온 것이다.


(2) resolve & reject

// Producer
const promise = new Promise((resolve, reject) => {
   console.log('doing something...'); 
   setTimeout(() => {
    //    resolve('seul');
    reject(new Error('이유를 잘 명시한다. ex) no network')) 
   }, 2000)
});


// Consumers
promise
.then((value) => {
    console.log(value);
})
.catch((error) => {
    console.log(error);
})
.finally(()=> {console.log('finally')})
;

// result ▼
// 'doing something...'
// (2초뒤) Error: 이유를 잘 명시한다. ex) no network
// finally

▲ 보통 에러라는 오브젝트를 통해서 값을 전달한다. Error 라는 클래스는 자바스크립트에서 제공하는 오브젝트중 하나이다. MDN-Error생성자

  • then , catch , finally 를 이용해서 값을 받아온다.
  • value 파라미터는 프로미스가 잘 수행 되어서 마지막으로 전달된 값이 들어온다.
  • Producer ) 성공적으로 수행되었다면 resolve를 호출, 실패했다면 reject를 전달한다.
  • Consumers ) promise를 이용해서 (then, catch, finally) 성공한 값 혹은 실패한 에러를 가져와서 원하는 방식으로 처리할 수 있다.



3-2. 프로미스 체이닝(chaining)

// 서버에서 숫자를 받아오는 프로미스
const fetchNumber = new Promise((resolve, reject) => {
    setTimeout(()=> resolve(1), 1000); // 1초후에 숫자 1 을전달
});

fetchNumber
.then(num => num*2)
.then(num => num*3)
.then(num => {
    return new Promise((resolve, reject) => {
        setTimeout(()=> resolve(num-1), 1000);
    })
})
.then(num => console.log(num)); 

// result ▼
// (2초뒤) 5
  • then 은 값을 바로 전달할 수도 있고, Promise 를 전달해도 된다. 이처럼 then 을 여러번 묶어서 처리할 수도 있다.
  • 프로미스 체이닝이 가능한 이유는 promise.then 을 호출하면 프로미스가 반환되기 때문이며, 반환된 프로미스엔 당연히 .then 을 호출할 수 있다.



3-3. 프로미스 체이닝 + 에러 핸들링(Error Handling)

(1) 프로미스 체이닝

const getHen = () =>
new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 1000);
});

const getEgg = hen =>
new Promise((resolve, reject) =>{
    setTimeout(() => resolve(`${hen} => 🥚`), 1000);
});

const cook = egg =>
new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
});

getHen()
.then(hen => getEgg(hen))
.then(egg => cook(egg))
.then(meal => console.log(meal));

// result ▼
// 🐓 => 🥚 => 🍳
  • 콜백함수를 전달할 때, 받아온 value를 다른 함수로 바로 하나로 호출할 경우에는 아래처럼 생략 가능하다.
    getHen().then(getEgg).then(cook).then(console.log);

(2) 에러 핸들링

const getHen = () =>
new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 1000);
});

const getEgg = hen =>
new Promise((resolve, reject) =>{
    setTimeout(() => reject(new Error(`${hen} => 🥚`)), 1000);
});

const cook = egg =>
new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
});

getHen()
.then(getEgg) 
// 이부분에서 발생하는 에러를 처리하고 싶다면
// 바로 뒤에 catch를 작성해서 바로바로 문제를 해결할 수 있다.
.catch(error => {
    return '🧀'; 
})
.then(cook)
.then(console.log)
.catch(console.log); 

// result ▼
// 🧀 => 🍳
  • 일부분에 문제가 발생하더라도 전체적인 처리에 문제가 발생하지 않도록 컨트롤 해줄 수 있다.
  • 마지막 행의 catch 는 도중에 에러가 발생했고, 에러처리가 안된 상황이라면, 해당 에러가 전달되어서 마지막에 호출된다.
    (위의 예제에서는 에러발생 지점 바로 뒤에 catch 를 작성해서 문제해결을 바로 한 것이다.🙂)



✍️ 궁금했던 것

then 은 성공했을 경우에만 실행되는 것 같은데, 그럼 왜 인수를 두 가지로 받는다는 설명이 있을까?🤔

  • then() 메소드는 두개의 콜백 함수의 인수를 받고 이행 및 거부 여부에 따라서 다르게 실행된다.
  • then() 문법 ▼
promise.then(
  function(result) { /* 결과(result)를 다룬다 */ },
  function(error) { /* 에러(error)를 다룬다 */ }
);
  • 성공적으로 이행된 프로미스 ▼
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

// resolve 함수는 .then의 첫 번째 함수(인수)를 실행한다.
promise.then(
  result => alert(result), // 1초 후 "done!"을 출력한다.
  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(errorHandlingFunction) 를 써도 된다. catchthennull 을 전달하는 것과 동일하게 작동한다.
  • 즉, then 으로 에러가 발생한 경우만 다루고 싶다면 then(null, errorHandlingFunction) 같이 null 을 첫 번째 인수로 전달하면 된다.
let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("에러 발생!")), 1000);
});

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

✍️ 작업이 성공적으로 처리된 경우만 다루고 싶다면 then 에 인수를 하나만 전달하면 된다. 🙂
✍️ .catch(f) 는 문법이 간결하다는 점만 빼고 .then(null,f) 과 같다.




reference
MDN-Error생성자
MDN-then
MDN-catch
MDN-promise
dream coding
promise
js-promise

좋은 웹페이지 즐겨찾기