[JavaScript]Promise

LMH·2022년 11월 23일
0
post-thumbnail

파일전송, 네트워크와 같이 비동기적으로 작업이 이루어 지는 경우 작업 시간은 단축될 수 있으나 흐름을 예측하기 어렵습니다.

개발자가 원하는 순서대로 함수를 호출하기 위해서는 콜백함수를 이용하여 순서를 정해 줄 수 있습니다. 하지만 콜백함수를 연속해서 사용하면 코드 가독성이 떨어지게 되는대 개발자들은 이것을 콜백 지옥이라 부릅니다.

const print = (num, func) => {
    setTimeout(double(num), Math.random() * 3000)
    func()
}
const double = (num) => {console.log(num*2)}
const printAll = () => {
	print(1, () => {
      print(2, () => {
         print(3, () => {
     	 })
      })
    })
}

printAll() 
// 2
// 4
// 6           

콜백 지옥을 해결하기 위해 Promise 객체를 사용합니다. Promise는 비동기 작업의 단위라 할 수 있습니다.

Promise는 사전 뜻은 "약속하다"로 의미 그대로 어떠한 로직을 처리하고 약속 된 값을 돌려 준다는 것을 의미합니다.

Promise를 사용할 때 제공자(Producer), 소비자(Consumer)로 나누어집니다. 데이터를 제공하는 것과 데이터를 사용하는 것에서 차이가 있습니다.제공자는 Promise를 생성하여 데이터 처리가 성공했는지 실패했는지에 따라 프로미스 객체를 리턴하여 주고, 소비자는 then, catch, fainally와 같은 메소드를 이용하여 데이터를 컨트롤 합니다.

// Poducer 
const promise = new Promise((resolve, reject) => {
	console.log('Promise');
});
// Promise 객체를 리턴

Promise 객체는 excutor라는 콜백함수를 인자로 받으며, excutro에는 resolve와 reject라는 두 가지 콜백함수를 인자로 받습니다. 이것을 코드로 나타내면 다음과 같습니다.

Promise 객체가 생성되면 자동으로 excutor 함수가 실행됩니다. 이는 네트워크 연결 시 불필요한 연결을 야기할 수 있으니 주의하여 사용해야 합니다.

Promise

Promise의 상태

  • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
  • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
  • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

Promise 객체는 resolve, reject라는 두 가지 콜백함수를 전달인자로 받습니다.

resolve

resolve는 Excutor함수 내에서 호출할 수 있는 함수로 resolve를 호출하는 것은 이 비동기 작업이 성공했다는 것을 의미합니다. 이행이 완료되면 resolve로 전달된 값을 가진채로 새로운 Promise 객체를 리턴합니다.

// Poducer 
const promise = new Promise((resolve, reject) => {
	console.log('Promise');
      resolve('success')  // Fullfilled 상태로 변환(이행)
//	  reject(new Error('Error')
});
// Promise 객체를 리턴

reject

reject는 Excutor함수 내에서 호출할 수 있는 함수로 resolve와 반대로 이 비동기 작업이 실패했다는 것을 의미합니다.(에러나 실패이유를 전달)

// Poducer 
const promise = new Promise((resolve, reject) => {
	console.log('Promise');
//    resolve('success') 
	  reject(new Error('Error') // Rejected 상태로 변환(실패)
});
// Promise 객체를 리턴

Promise 체이닝

Promise 객체는 이행되고 나면 Promise 객체를 리턴하기 때문에 내장메소드를 활용해 체이닝이 가능합니다. fetch API 예시로 Promise 체이닝을 구현해 보겠습니다.

fetch API는 브라우저에서 동작가능한 인터페이스로 특정 URL에 요청을 보내면 응답으로 데이터를 보내줍니다.

URL : https://koreanjson.com/

then

Promise 객체가 이행될 경우 내장 메소드인 then을 이용하여 데이터를 전달 받을 수 있습니다.
fetch를 호출하면 Promise 객체가 리턴되어 then 메소드 사용이 가능합니다. then 메소드를 통해서 Promise 객체 내부에서 resolve로 전달된 값을 확인할 수 있습니다.

const getData = () => {
	fetch('https://koreanjson.com/users')
    .then(response => { return response.json() // JSON 데이터를 자바스크립트 객체로 변환 
     console.log(response)  
    })
}
getData() // Response {type: 'basic', url: 'https://koreanjson.com/users', redirected: false, status: 200, ok: true, …}                         

then 메소드를 호출할 경우 리턴되는 값이 또 Promise 이므로 반복해서 then 메소드를 사용할 수 있습니다. 전달된 data를 확인한 결과, 10개의 객체 데이터를 포함한 배열을 확인할 수 있습니다.

const getData = () => {
	fetch('https://koreanjson.com/users')
    .then(response => { return response.json()})
    .then((data) => console.log(data))
}

getData() // (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]

catch

Promise 객체가 거부되는 경우 내장 메소드인 catch를 사용하여 에러를 핸들링 할 수 있습니다.

const getData = () => {
	fetch('https://koreanjsonssss.com/users') // 잘못된 URL 입력
    .then(response => { return response.json()})
    .catch(error => console.log(error));
}
getData() // TypeError : Failed to fetch

finally

이행되거나 거부되어도 사용할 수 있는 내장 메소드입니다. 아래의 코드를 보면 Promise 객체가 이행되어 conconsole.log(data)와 console.log('completed') 모두 잘 출력되는 것을 알 수 있습니다. 만약 거부가 발생하더라고 console.log('completed') 여전히 잘 작동합니다.

const getData = () => {
	fetch('https://koreanjson.com/users')
    .then(response => { return response.json()})
  	.then(data => { return console.log(data)})
    .catch(error => console.log(error))
    .finally(console.log('completed'))
}
getData() 
// completed
// (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]

Promise.all

Promise.all 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환합니다. 주어진 Promise 중 하나가 거부하는 경우, 첫 번째로 거절한 Promise를 이유를 사용해 자신도 거부합니다.

즉, Promise는 객체를 순회하며, 각 Promise 요소가 이행되면 resolve를 통해 전달된 값을 배열에 추가하고 하나라도 거부될 경우 거부하는 로직을 가지고 있습니다. 또한, 순회 도중 Promise가 아닌 데이터가 있어도 그대로 배열에 포함시켜 줍니다.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// Array [3, 42, "foo"]

async/await

async/await은 Promise의 Syntax sugar로 Promise Hell을 방지하고 가독성 좋은 코드를 만드는데 사용합니다.
기존에 Producer을 만드는 방법(콜백함수, Promise)과는 관련이 없고 체이닝 과정에서 await을 이용하어 Promise가 끝날 때 까지 기다리고 결과 값을 특정 변수에 담을 수 있습니다.

await을 사용하면 resolve에 전달된 값이 나옵니다. 아래의 코드에서는 then 메소드를 활용하여 Promise 체이닝을 하고 awit 키워드를 이용하여 최종적으로 전달된 값을 변수에 할당하는 것을 볼 수 있습니다.

const getData = async () => {
const data = await fetch('https://koreanjson.com/users')
			       .then(response => { return response.json()})
				   .catch(error => console.log(error))
    			   .finally(console.log('completed'))  
  	
console.log(data)
}
// completed
// (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]

정리

await 키워드를 사용면 콜백, 프로미스 지옥에서 벗어나 가독성 좋은 코드를 들어 줄 수 있습니다.

profile
새로운 것을 기록하고 복습하는 공간입니다.

0개의 댓글