[Javascript] 비동기 구현 방식 - Promise

짱쫑·2021년 12월 12일
0
post-thumbnail

::Promise

프로미스는 자바스크립트 안에 내장되어 있는 object이다. 프로미스 오브젝트는 Asynchronous operatoration을 위해 쓰이는데 비동기적 작업을 수행할 때 callback함수 대신에 쓰인다.

  1. State(상태): 프로세스가 무거운 작업을 수행중인지 수행이 완료 되었고 성공했는지 실패했는지 이런 '상태'에 대해서 이해해야한다.

::상태

  • pendding: operation이 실행 중인 상태
  • fulfilled: operation이 성공적으로 끝난 상태
  • rejected: operation이 실패한 상태
  1. Producer와 Consumer: 원하는 데이터를 제공(Producing)하는 사람(Producer)과 이 데이터를 쓰는사람(Consumer)의 차이점을 이해해야한다.

  • promise 만들기
  1. Producer 만들기
//프로미스는 class이기 때문에 new를 이용해서 오브젝트를 생성할 수 있다.
//프로미스의 생성자를 보면 executor라는 콜백함수를 전달해 줘야하는데, 콜백함수에는 또다른 두가지의 콜백함수를 받는다. 
//1. resolve : 기능을 정상적으로 수행해서 최종데이터를 전달하는 콜백함수
//2. reject : 기능을 수행하다 중간에 문제가 생기면 호출하게 될 콜백함수
const promise = new Promise(resolve, rejecet) => {
	// 보통은 프로미스 안에서 무거운 작업을 수행하는데, 파일에서 어떤 큰 데이터를 읽어오는 것은 시간이 걸리는 작업인데, 그것을 동기적으로 처리해버리면 파일을 읽어오고 네트워크에서 받아오는동안 다음 코드가 실행되지 않기 때문에 프로미스를 이용해 비동기적으로 처리하는 것이 좋다. (network, read files)
});
// promise object 생성 완료

프로미스를 만드는 순간 executor라는 콜백함수가 실행된다. 프로미스안에 네트워크와 연결되는 작업을 넣었다면 프로미스가 만들어지는 순간 네트워크 통신 작업을 수행한다.

만약, 사용자가 버튼을 눌렀을 때만 네트워크를 요청을 해야 된다면 위의 코드는 사용자의 의지와는 상관없이 무조건 네트워크 통신을 하게 된다.

const promise = new Promise(resolve, rejecet) => {
    //setTimeout을 이용해 시간의 딜레이를 주면
	setTimeout(() => {
      //기능을 성공적으로 수행했다면 resolve 콜백함수를 호출한다.
      //사용자의 이름은 zzang이다 라고 한다면
      resolve('zzang')
      //만약 실패한다면
      reject(new Error('no network'))
      //2초 뒤 실행
    }, 2000);
});

이 프로미스는 어떤 일을 2초뒤 실행을 성공적으로 수행해서 zzang이라는 값을 전달해주게 된다.

  • promise 사용하기
  1. consumers 만들기
    consumer는 then, catch, finally를 이용해 값을 받아 올 수 있다.
//위에 만든 프로미스 변수를 활용해 promise값이 정상적으로 수행이 된다면(then)
//value의 값을 받아와서 내가 원하는 콜백함수를 전달해주면 된다. 
//여기에 value라는 파라미터는 위의 promise가 정상적으로 수행이 되어 resolve에서 전달된 zzang이라는 값이다. 
promise.then((value) => {
	
})

자 여기까지만 하면 성공했을 때의 값만 불러온다. 근데 실패했다면 어떻게 해야 하는가?

promise
  .then((value) => {
	
})
  //catch를 통해 위의 코드에서 error를 받아올 수 있다.
  .catch(error => {
    
});

여기서 프로미스의 then을 호출하게 되면 then은 결국 똑같은 프로미스를 리턴하기 때문에 그 리턴된 프로미스의 catch를 호출 할 수 있다. 이것을 체이닝(Chaining)이라 한다.
then을 호출하게 되면 다시 프로미스가 리턴되고 리턴된 프로미스에 catch를 등록하는 것이다.

프로미스 오브젝트를 만들때 비동기적인 작업(기능)을 작성하고 성공적으로 수행했다면 resolve를 호출하고, 실패했다면 reject(실패한것과 실패한 이유)를 호출해 error를 전달한다. 나중에 프로미스를 이용해 then과 catch를 이용해 성공한 값과 실패한 error를 받아와 원하는 방식으로 처리해주면 된다.

promise
  .then((value) => {
	console.log(value);
})
  .catch(error => {
    console.log(error);
});
  //finally는 성공실패 유무와 상관없이 마지막에 호출되어 진다.
  //성공과 실패 상관없이 다음 작업을 넣고 싶을 때 finally를 사용한다.
  .finally(() => {
  	console.log('finally');
});
  • promise 연결하기
  1. promise chaining
//서버에서 숫자를 받아오는 새 프로미스를 만든다고 가정하자
const fetchNumber = new Promise((resolve, reject) => {
    //서버통신을 해야하니 1초 있다가 숫자 1을 전달해주는 프로미스를 만들었다.
	setTimeout(() => resolve(1), 1000);
});

fetchNumber
 //fetchNumber가 성공적으로 완료되면 숫자에 2를 곱하고
 //여기서 num은 위의 resolve값인 1이다.
 .then(num => num * 2) // 2
 //그 숫자에 3을 곱하고
 .then(num => num * 3) // 6
 //그 숫자를 다른 서버에 보내 다른 숫자로 변환된 값을 받아온다.
 .then(num => {
    //새로만든 프로미스는 다른 서버와 통신을 한다
 	return new Promise((resolve, reject) => {
        //숫자에 1을 뺀 값을 다시 resolve를 통해서 전달한다.
    	setTimeout(() => resolve(num - 1), 1000)
    })
 });
 //그 숫자를 출력한다
 .then(num => console.log(num)); // 5

최종적으로 수행되는 데 걸리는 시간은 총 2초이다.
유의할 점은 then은 값을 바로 전달할 수도 있고, promise를 전달할 수도 있다.
이렇게 여러 비동기적인 친구들을 묶어서 사용하는것이 체이닝(chaining)이다.

  • 오류처리
  1. Error handling
    프로미스를 chaining했을 때 어떻게 에러 관리를 할까?
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)); 
  
  * 이 것을 좀더 깔끔하게 만들면
  //한가지만 받아서 그대로 전달하는 경우는 생략가능
  getHen() // 프리티어 포맷에서 밑과 같이 한줄로 바꾸는데 가독성이 좋지 못하다 그래서 //를 붙이면 한줄처리를 하지 않는다. 
  .then(getEgg)
  .then(cook)
  .then(console.log); 
  ==
  getHen().then(getEgg).then(cook).then(console.log); 

이제 에러를 추가해보자

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

getHen() //
  .then(Egg)
  .then(cook)
  .then(console.log)
  .catch(console.log)

마지막의 catch를 통해 에러를 잡을 수 있다.

만약 여기서 닭이 알을 낳지 못해 다른것으로 대체해야 한다는 상황을 가정하면

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

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

계란 수급이 되지 않아도 빵으로 대체가 가능하도록 만들었다.
이 상황에 catch를 이용해 에러를 받아오지 못하게 되면 콘솔창에는 잡히지 않는 에러(Uncaught error)가 뜰 것이다.

profile
不怕慢, 只怕站

0개의 댓글