/ JAVASCRIPT

JavaScript(10) - promise

JavaScript 관련 포스팅

promise

  • 비동기(asynchronous)를 callback fn 대신 간편하게 처리하도록 도와주는 내장 object
  • 정해진 장시간의 기능을 수행 후
    • 정상적으로 기능이 수행이 되었다면, 성공 메시지처리된 결괏값을 전달해줌
    • 만약 기능 수행 중 예상치 못한 문제가 발생하였다면, 에러를 전달해줌

포인트

  1. State [상태] 이해
    프로세스가 heavy한 명령을 수행하고 있는지, 기능 수행 완료 후 성공했는지, 실패했는지

  2. Producer vs. Consumer
    Producer: 우리가 원하는 데이터를 제공하는 사람
    Consumer: 제공된 데이터를 쓰는/필요로 하는 사람

State [promise의 상태]

promise가 만들어져서,

  • 지정한 operation을 수행하고 있는 중이라면 pending상태
  • operation을 성공적으로 끝내게 되면 fulfilled상태
  • file을 찾을 수 없거나 네트워크에 문제가 생기면 rejected상태

Producer vs. Consumer

  • Producer
    원하는 기능을 수행해 해당하는 데이터를 만들어내는 promise object
  • Consumer
    원하는 데이터를 소비

1. Producer

  • Promise 만들기
  • class라서 new 키워드를 이용해 object를 생성할 수 있음
  • promise 생성자를 보면 executor라는 callback fn을 전달해주어야 함
    • executorresolve (기능이 정상 수행되어 결괏값을 전달함)와
      reject (기능 수행 중 문제가 생기면 호출함) 두 개의 callback fn으로 구성됨
var Promise: PromiseConstructor new <any>(executor: (resolve: (value: any) => void, reject: (reason?: any) => void) => void) => Promise<any>
const promise = new Promise((resolve, reject) => {
// doing some heavy work(network, read files)
console.log('doing something...');
setTimeout(() => {
    resolve('success!');
}, 2000);
});

→ 어떤 일을 2000ms(=2sec)정도 하다가 잘 마무리 해서 resolve (callback fn)를 호출하여 ‘success!’라는 값을 전달하는 Promise

  • promise안에서는 다소 heavy한 일을 함
    네트워크에서 데이터를 받아오거나 파일에서 큰 데이터를 읽어오는 과정은 시간이 꽤 걸림
    시간이 많이 소요되는 과정들을 과정들을 동기적(synchronous)으로 처리하는 동안에 다음 라인의 코드는 실행되지 않게 됨
    그래서 시간이 좀 걸리는 일들은 Promise를 만들어 비동기적으로 처리하는 것이 좋음

※주의※
Promise를 만드는 순간, 우리가 만든 executor(callback fn)가 바로! 자동 실행

2. Consumer

  • producer를 사용하는 주체
  • then, catch, finally 로 값을 받아옴

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

    ⇒ 위에서 만든 promise가 정상적으로 수행이 된다면(then),
    value라는 parameter를 받아오는 callback fn을 수행하게 됨

    ※ 여기서 value는 resolve(callback fn)으로 전달된 ‘success!’라는 값

Console
doing something…
success!

resolve가 아닌 reject를 사용한다면?

  • reject는 주로 Error object를 통해 값을 전달함
  • Error object에는 어떤 error가 발생했는지 이유를 잘 명시해야함

      const promise = new Promise((resolve, reject) => {
          // doing some heavy work(network, read files)
          console.log('doing something...');
          setTimeout(() => {
              // resolve('success!');
              reject(new Error('no network'));
          }, 2000);
      });
    
    Console
    doing something…
    ▶ Uncaught (in promise) Error: no network at promise.js:16:16
  • console장에 Uncaught (잡히지 않는)라고 뜨는 이유는 then 으로 성공적인 케이스만 다뤘기 때문임

catch 를 이용해 error가 발생했을 때 어떻게 처리할 지 callback fn을 등록해주면 됨

promise 
    .then((value) => {
        console.log(value);
    })
    .catch(error => {
        console.log(error);
    });
Console
doing something…
Error: no network at promise.js:15:16

catch의 등록으로 더는 error를 생성하지 않게 됨

then과 catch를 chaining

  • promise의 then을 호출하면, 똑같은 Promise를 리턴
    리턴된 Promise에 catch를 호출할 수 있게 됨
    (array에서 map, sort, join함수를 중첩 사용하는 것도 chaining)
(method) Promise<any>.then<void, never>(onfulfilled?: (value: any) => void | PromiseLike<void>, onrejected?: (reason: any) => PromiseLike<never>): Promise<void>

finally는 성패와 상관 없이 무조건 마지막에 호출됨

const promise = new Promise((resolve, reject) => {
    console.log('doing something...');
    setTimeout(() => {
        resolve('success!');
        // reject(new Error('no network'));
    }, 2000);
});

promise 
    .then((value) => {
        console.log(value);
    })
    .catch(error => {
        console.log(error);
    })
    .finally(() => {
        console.log('finally!');
    });
Console
doing something…
success!
finally!
const promise = new Promise((resolve, reject) => {
    console.log('doing something...');
    setTimeout(() => {
        // resolve('success!');
        reject(new Error('no network'));
    }, 2000);
});

promise 
    .then((value) => {
        console.log(value);
    })
    .catch(error => {
        console.log(error);
    })
    .finally(() => {
        console.log('finally!');
    });
Console
doing something…
Error: no network at promise.js:15:16
finally!

3. Promise chaining

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

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

4. Error handling

Original code

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));
Console

🐓 => 🥚 => 🍳

tip

.then에서 받아오는 value를 바로 callback fn의 parameter로 전달할 때, 중복되는 value는 생략이 가능함

getHen()
.then(getEgg)
.then(cook)
.then(console.log)

Error 발생

const getHen = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 1000);
});
const getEgg = hen => new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
});
const cook = egg => new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
});
Console
▶ Uncaught ReferenceError: Cannot access ‘getHen’ before initialization at promise.js:62:1

Error를 .catch로 잡아주면,

getHen()
.then(getEgg)
.then(cook)
.then(console.log)
.catch(console.log)
Console
Error: error! 🐓 => 🥚 at promise.js:71:29

.then뒤에 .catch로 바로 error를 처리해주면 됨

getHen()
.then(getEgg)
.catch(error => {
    return '🥖';
})
.then(cook)
.then(console.log)
.catch(console.log)
Console

🥖 => 🍳

< 출처 >

“자바스크립트 12. 프로미스 개념부터 활용까지 JavaScript Promise | 프론트엔드 개발자 입문편 (JavaScript ES6),” 유튜브 비디오, 28:09, 게시자 “드림코딩 by 엘리,” 2020년 6월 18일, https://youtu.be/tJieVCgGzhs