Frontend Study - 2/Javascript

Javascript : 동기와 비동기 (3) Promise

갓데미 2022. 9. 6. 22:55

Promise. 프로미스는 자바스크립트 비동기 처리에 사용되는 객체이다. 무겁고 오래 걸리는 일을 비동기적으로 처리할 때 유용하게 쓰인다.

프로미스는 어떤 비동기적인 일을 실행했을 때 그 일이 끝난 이벤트, 즉 끝났을 때를 알려준다.

비동기적으로 수행한 결과값 (성공 or 실패) 을 알려주는 오브젝트이다.

 

앞선 콜백 함수 파트에서는 위 과정을 콜백함수를 통해 진행하였다.

다만 복잡한 작업의 경우 콜백지옥이 발생하고 가독성과 유지보수 측면에서 bad했다.

 

Promise를 통해서 해결 가능한 부분이다.

 

프로미스는 생성되고 종료될 때까지 세가지 상태를 가진다.

- Pending (대기) : 비동기 처리 로직이 완료되지 않은 상태

- Fulfilled (이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태

- Rejected (실패) : 비동기 처리가 실패하거나 오류가 발생한 상태 

 

 

펜딩상태는 실행 전 상태이다.

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

Promise를 호출만 하고 아직 실행하지 않았다. new Promise를 호출할 때 resolvereject 콜백 함수를 인자로 선언할 수 있다. 

 

 

resolve()를 통해 실행하였고, Fulfilled 상태가 되었다.

function getData() {
	new Promise((resolve, reject) => {
	const data = 10;
	resolve(data);
	});
}

getData()
	.then((resolvedData) => {console.log(resolvedData);});

Promise의 Api인 .then의 첫 번째 인수는 프로미스가 이행되었을 때 실행되는 함수이다. 프로미스를 선언한 함수내의 resolve(*) 안의 값을 인자로 전달 받는다. resolve(data) 의 data를 인자로 받게 되는 것이다.

첫번째 인자와 같이 두번째 인자를 넣어서 에러가 났을때 실행되는 함수를 지정할 수 도 있지만 보통 .then보다는 .catch를 통해 잡아낸다.

 

 

실패가 되면 reject() 실패 상태가 된다.  

function getData(data) {
	return new Promise((resolve, reject) => {
    	if (data < 0) {
        reject(new Error("error!")
        } resolve()
    }
}

getData(-2)
	.then(() => {console.log("success")})
	.catch((err) => {console.log(err)})  
    
    //error!

 

resolve( * )와 reject( # )안의 값, 즉 결과값을 then 과 catch를 통해 전달 받는 다는 것을 이해하는 것이 중요하다.

.then(( * ) => { console.log( * ) })

.catch(( # ) => { console.log( # })

 

.finally는 성공 or 실패 작업까지 다 처리가 된 뒤에 실행할 일이다.

.finally(() => { console.log("well done!" })

 

실패했을 경우와 마지막 finally는 생략이 가능하다. resolve하나만 적고 성공하는 케이스에 대해서만 작성 가능하다

 

 

Promise의 API를 사용한 기본구조 예시이다.

function runInDelay(seconds) {
	return new Promise((resolve, reject)=> {
	if (!seconds || seconds < 0) {
	reject(new Error(“error”))}
	setTimeout(() => {
	resolve()} , seconds*1000);
}}

runInDelay(2)
	.then(() => {
	console.log(“타이머 완료!’);
	})
	.catch(console.error)
	.finally(() => console.log(“끝”);

자세하게 보자면,

먼저 runInDelay(2) 라는 함수가 실행이 되겠다. 

그 뒤에 붙는 .then, .catch, .finally는 얘네들은 실행이 완료된 이후에 생각하면 되는 것들이다. 우선 runInDelay(2)를 보면된다.

선언된 runInDelay 함수를 보니 프로미스 함수이고, 파라미터인 seconds가 없거나 0보다 작으면 reject로 새로운 에러를 생성한다. (이 에러 값은 .catch뒤의 함수의 인자로 전달된다.)

에러가 없이 실행이 된다면 지정한 seconds초 뒤에 resolve()를 실행함으로서 결과값을 then(f) 함수의 인자로 전달한다.

위의 사례는 비어 있기 때문에 따로 전달하는 값은 없다.

seconds 인자로 잡혀있는 2는 0보다 크기 때문에  seconds초가 지나고 resolve()가 실행된다.

에러가 나지 않고 실행되었기 때문에 .then() 에 들어있는 함수 console.log("타이머 완료!") 를 실행하면 된다.

 

 

+ Promise chain

 

여러개의 프로미스를 연결해서 사용하는 것을 말한다.

프로미스를 리턴하면서 then으로 그 값을 받아드리고 다시 프로미스를 리턴하고 다시 then으로 값을 받아드리고..

비동기적 처리들을 순차적으로 처리하는 프로미스 체인을 만들 수 있다.

function getData() {
	return new Promise ((resolve, reject) => {
	...
	}
}

function parseData(newData) {
	return Promise.resolve((newData) => { ...//parse(newData) })
    

function displayData(parseData) {
	return Promise.resolve((parseData) => {... //display(parseData)})
    
    

getData()
	.then((newData) => {parseData(newData)})
	.then((parseData)=> {displayData(parseData)};

 

추가 응용으로, 프로미스 체이닝 중에 에러가 발생했을 때 잡아내고 싶다면,

.then .then .then 이후 마지막에 .catch 처리를 해줘서 에러를 잡아낼 수 도 있지만

처음에 에러를 잡은 다음에 다른 값을 return하여 대체하는 방식으로 사용할 수 도 있다.

 

getData()
  .catch((error) => {
    console.log(error.name);
    return "subdata";
  })
  .then((data) => parseData(data))
  .then((parseData) => displayData(parseData))

 

 

Promise.all 병렬처리 

function getBanana() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("🍌");
    }, 1000);
  });
}

function getApple() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("🍎");
    }, 3000);
  });
}


let fruits = [];
function pushToArray(fruit) {
  fruits.push(fruit);
}


Promise.all([getBanana(), getApple()]).then((fruit) => {
  pushToArray(fruit);
  console.log(fruits);
});

//엘리 드림코딩 예시참고

 

promise.all 은 병렬적으로 한번에 모든 Promise들을 실행하게 한다.

아래와 같은 방식으로는 바나나 1초 + 사과 3초, 총 4초가 걸리고 위의 병렬처리의 경우 같이 진행되므로 총 3초가 걸리게 된다. 


getBanana().then((banana) =>
  getApple().then((apple) => {
    [banana, apple];
  })
);


//엘리 드림코딩 예시참고

 

 

Promise.race 

주어진 Promise 중에 가장 빨리 수행된 것만 실행된다.

Promise.race([getBanana(), getApple()])
	.then((fruit) => console.log(fruit));

 

바나나가 1초 뒤의 호출로 빠르기 때문에 바나나만 호출된 상태로 끝나게 된다.

 

 

 

 

 

 

참고

https://academy.dream-coding.com/courses/javascript

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

https://ko.javascript.info/promise-basics