TIL31⎟JavaScript : Promise로 콜백함수 해결하기

itssweetrain·2021년 3월 10일
0

JavaScript 

목록 보기
22/25
post-thumbnail

콜백을 대체해서, Promise로 어떻게 깔끔하게 작성할 수 있는지 살펴보자! (⁎⁍̴̛ᴗ⁍̴̛⁎)

📕 Promise

자바스크립트에서 제공하는 비동기를 간편하게 처리할 수 있도록 도와주는, javaScript안에 내장되어져 있는 object이다

비동기적인 것을 수행할 때 콜백 함수 대신에 유용하게 쓸 수 있는 그런 object!

1-1. Promise 문법..!

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

Promise는 class이기 때문에 new라는 keyword를 통해서 object를 생성할 수 있고, new Promise에 전달되는 함수는 executor, 실행자로 콜백함수가 들어간다! 이 콜백함수에는 또 다른 두가지의 콜백함수를 받는다.

executor의 인수 resolve와 reject는 javaScript의 자체적인 콜백함수이며, 우리는 executor에서 결과를 즉시 얻든, 늦게 얻든 인수로 넘겨준 콜백 중 하나를 반드시 호출해야 한다.

기능을 정상적으로 수행해서 마지막에 최종 데이터를 전달하는 resolve 콜백함수와 중간에 기능을 수행하다가 중간에 문제가 생기면 호출하게 될 reject 두 가지로 나누어져 있으며, 이것을 받는 콜백함수를 만들어 promise object를 만든다.

정해진 장시간의 기능을 수행하고 나서 정상적으로 기능이 수행되어졌다면, 성공의 메세지와 함께 처리된 결과값(value)을 전달해주며(resolve), 만약 기능을 수행하다가 예상치 못한 문제가 발생했다면 에러 객체를 나타내는 error를 전달해준다.(reject)

💡요약

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

1-2. heavy한 일들을 처리하는 Promise...!

보통은 promise안에서 조금은 heavy한 일들을 한다! 왜냐면 우리가 네트워크에서 데이터를 받아오거나 파일에서 큰 데이터를 읽어오는 그런 과정은 시간이 꽤 걸린다. 그런 것을 동기적, synchornous로 처리하게 되면 우리가 파일을 읽어오고 네트워크에서 데이터를 받아오는 동안 그 다음 라인에 코드가 실행되지 않기 때문에 시간이 조금 걸리는 일들은 이렇게 promise로 만들어서 비동기적으로 처리하는 것이 좋다.

1-3. runs automatically...!

when new Promise is created, the executor runs automatically
여기서, 우리가 Promise를 만드는 순간, 우리가 전달한 executor라는 콜백함수가 바로 실행되는 것을 확인할 수 있다.

이 말인 즉슨, 만약 우리가 이 Promise안에 네트워크 통신을 하는 그런 통신을 작성했다면 Promise가 만들어지는 그 순간, 네트워크 통신을 수행하게 된다.

여기서 한 가지 중요한 포인트는, 만약 네트워크 요청을 사용자가 요구했을 때만 해야 되는 경우라면 예를 들어 사용자가 버튼을 눌렀을 때만 네트워크 요청을 해야 되는 경우라면 이런 식으로 작성하게 되면 사용자가 요구하지도 않았는데 불필요한 통신이 일어날 수도 있다. 그래서 Promise를 만드는 순간 그 안에 전달한 executor라는 콜백 함수가 바로 실행이 되기 때문에, 이 점을 유의해서 공부해야 한다.

이 사실을 조금 간과하고 지나갔다가 불필요한 네트워크 통신을 하는 경우가 있다.

Promise에서 두 가지를 기억하자!

  1. producer와 consumer의 차이점을 아는 것
    우리가 원하는 데이터를 producing, 제공하는 사람과 이 제공된 데이터를 쓰는 사람, 필요로 하는 사람 소비자 consumer 이 두 가지의 차이점을 잘 이해하자.
  2. state와 result
    프로세스가 헤비한 오퍼레이션을 수행하고 있는 중인지 아니면 이 기능 수행이 다 완료가 되어서 성공했는지 실패했는지 이런 상태에 대해서 이해하는 것이 중요

1-4. state 와 result

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

executor는 아래와 같이 promise의 상태를 둘 중 하나로 변화시킨다.

  • Promise의 상태는 Promise가 만들어져서 우리가 지정한 오퍼레이션이 수행중일 때는 pending 상태, 이 오퍼레이션을 성공적으로 다 끝내게 되어 resolve가 호출되면 value(result) 값을 출력함과 동시에 fulfilled state가 된다.

  • 아니면 파일을 찾을 수 없거나 네트워크에 문제가 생겨 reject가 호출되면 error(result) 값을 출력함과 동시에, rejected 상태가 된다.

1-5. producer 와 consumer

Promise에는 우리가 원하는 기능을 수행해서 해당하는 데이터를 만들어내는 Producer vs 우리가 원하는 데이터를 소비하는 Consumer 로 나눠진다.

  1. producer
    우리가 원하는 기능을 비동기적으로 실행하는 Promise를 만들어보자

이 Promise안에서 네트워크를 통제하는 것처럼 setTimeout을 이용해서 시간을 딜레이주도록 해보자. setTimeout은 스케줄링에 사용되는 가장 대표적인 함수!

const promise = new Promise((resolve, reject) => {
    //doing some heavy work (network, read files)
    setTimeout(()=> 
        resolve('ryan'), 1000);    
});        

1초 뒤에 일이 성공적으로 수행했다는 신호가 전달되면서 resolve 콜백함수가 호출되어 ryan이라는 값을 전달해주는 Promise!

const promise = new Promise((resolve, reject) => {
    //doing some heavy work (network, read files)
    setTimeout(()=> 
        reject(new Error('error occur')), 1000);    
});        

1초 뒤에 reject가 호출되며 rejected state로 변하게 된다.

❗️ Promise 객체의 state, result 프로퍼티는 내부 프로퍼티이므로 우리가 직접 접근할 수는 없지만, .then .catch .finally 메서드를 사용하여 접근할 수 있다!

  1. consumer
    이렇게 producer를 만들어봤고, 이제 consumer를 만들어보자. then, catch, finally 메서드를 이용해서 값을 받아올 수 있다.
  • then
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000);
});
//resolve함수는 .then의 첫 번째 함수를 실행한다
promise.then(
result => alert(result),
error => alert(error)
);

then이라는 것은 이 promise가 정상적으로 잘 수행이 되어서 마지막에 최종적으로 resolve라는 콜백함수를 통해서 전달한 이 값이 여기 value의 parameter로 전달이 되어서 들어오는 것을 볼 수 있다. 첫 번째 함수 alert가 1초 후 done!을 출력하며 실행된다

우리가 만약 resolve 말고 reject를 쓰게 된다면? 만약 우리가 네트워크를 하다가 무언가 실패를 해서, resolve 대신에 reject를 호출해보도록 하자.
promise가 거부된 경우에는, 아래와 같이 두 번째 함수가 실행된다

reject는 보통은 error라는 object를 통해서 값을 전달하는데, 여기서 error라는 클래스는 자바스크립트에서 제공하는 object중 하나이다. 그래서 error는 무언가 error가 발생했다는 걸 나타내는 object인데, 이것도 다루려면 꽤 양이 많아서 다음의 error handling part에서 보자

const promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("error occur")), 1000);
});
//reject함수는 .then의 두 번째 함수를 실행한다
promise.then(
result => alert(result),
error => alert(error)
);
promise
.then(value => {
    console.log(value)
})
.catch(error => {
    console.log(error);
})
.finally(()=> {
    console.log('finally');
});

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

에러가 발생한 경우만 다루고 싶다면,

  • catch
const promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("error occur")), 1000);
});

promise.catch(alert)
//1초 뒤 error occur 출력

최근에 추가 되어진 것인

  • finally
    성공하든 실패하든 성공여부와 상관없이 어떤 기능을 마지막으로 수행하고 싶을 때 사용할 수 있다. 무조건 마지막에 호출된다. 아무런 인자를 받지않고 콘솔로그를 출력하게 되면..그래도 출력된다.

2. Promise Chaining

다른 비동기적인 여러가지 then을 동시에 묶어서 처리할 수도 있다.

우리가 서버에서 숫자를 받아오는 새로운 promise를 만들어보자!

const fetchNumber = new Promise((resolve,reject) => {
    setTimeout(()=> resolve(1), 1000);
});

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));
//2초뒤에 5가 출력되는 것을 볼 수 있다

then은 값을 바로 전달할 수도 있고, 아니면 promise를 전달해도 된다. 이것이 최종적으로 출력되는 데까지 걸리는 시간은 2초가 소요된다.

3. Error Handling

이렇게 promise를 chaining했을 때, 어떻게 에러를 핸들링할 수 있는지에 대해 알아보자!

error가 생긴다면, 다른 것을 전달해주도록 하자. 즉, 전달되어 진 error를 잘 처리해서 문제가 생겨도 전체적인 promise chain에 문제가 생기지 않도록 메꾸는 작업을 해줘야한다. .catch를 이용해 바로바로 이용해주도록 한다.

총 3가지의 Promise를 return하는 함수들

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));
    
콜백함수를 전달할 때, 받아오는 value를 다른 함수로 바로 호출하는 경우에는, 중복되는 것을 생략할 수 있다. then에서 받아오는 value를 바로 getEgg로 전달해준다. 

getHen()
    .then(getEgg)
    .then(cook)
    .then(console.log);
    
    //
    🐓 => 🥚 => 🍳
    
hen을 받아오고 나서 받아와지면, 전달받은 hen을 이용하여 getEgg함수를 호출
정상적으로 수행이 되면, 받아온 달걀을 가지고 cook, cook이 되면 문자열이 출력이 되면, meal을 console.log에 출력해보자

만약 달걀을 받아오는 부분에서 네트워크에 문제가 생겨서 실패하게 된다면, new Error object를 이용하지.

const getEgg = hen =>
    new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
    });
//Uncaught (in promise) Error: error! 🐓 => 🥚

error handling을 하지 않아서, 빨간색 글씨로 에러가 발생!제일 마지막으로 catch를 작성하게 되면,

getHen()
    .then(getEgg)
    .then(cook)
    .then(console.log);
    .catch(console.log);
//
Error: error! 🐓 => 🥚

달걀을 받아오는 부분에서 에러가 발생했지만, 에러가 밑으로 전달되면서 catch가 잡혀지는 것을 볼 수 있다.

getHen()
    .then(hen => getEgg(hen))
    .catch(error => {
        return '🥖';
    })
    .then(egg => cook(egg))
    .then(meal => console.log(meal))
    .catch(console.log);
//
🥖 => 🍳

계란을 받아오는 것은 실패했지만 대신 빵을 받아오고, 여기서 발생하는 error을 처리하고 싶으면 바로 다음에 catch로 처리해준다. 순서를 변경해가면서 처리를 할 수 있다.

profile
motivation⚡️

0개의 댓글