본문 바로가기
Programming/자바스크립트

[자바스크립트] 프로미스 - 개념 & 기본 사용법

by 코딩하는 랄로 2023. 11. 6.
728x90

프로미스의 탄생 : 콜백지옥

자바스크립트에서는 서버에서 데이터를 받아오는 등 시간이 걸리는 작업을 처리를 할 때, 대게 비동기함수를 사용하여 처리한다. 비동기는 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하기 때문에, 비동기 작업의 결과에 따라 다른 작업을 수행해야 할 때에는 콜백 함수를 사용해 왔다.

 

콜백 함수는 비동기 작업이 완료되면 자바스크립트 엔진의 콜백 큐로 다시 불러오는(call back) 함수의 의미로, 비동기 함수의 매개변수로 함수 객체를 넘기는 것을 뜻한다. 이런 콜백 함수를 통해 비동기 작업의 결과로 그 다음의 작업을 수행할 수 있었던 것이다.

 

하지만 콜백함수를 사용하면 코드가 복잡하고 가독성이 떨어지는 단점이 있다. 여러 개의 비동기 작업을 순차적으로 수행하다보면, 콜백 함수가 중첩되어 코드가 갈수록 안쪽으로 들어가는 현상인 콜백 지옥이 발생하는 것이다.

 

 

콜백지옥 예제

아래의 코드는 숫자 n을 파라미터로 받아와서 다섯번에 걸쳐 1초마다 1씩 더해서 출력하는 작업을 비동기 함수로 구현한 코드이다.

function increaseAndPrint(n, callback) {
  setTimeout(() => {
    const increased = n + 1;
    console.log(increased);
    if (callback) {
      callback(increased); // 콜백함수 호출
    }
  }, 1000);
}

increaseAndPrint(0, n => {
  increaseAndPrint(n, n => {
    increaseAndPrint(n, n => {
      increaseAndPrint(n, n => {
        increaseAndPrint(n, n => {
          console.log('end');
        });
      });
    });
  });
});

 

 콜백 함수를 5번만 사용하였음에도, 코드가 복잡해보이고 가독성이 현저히 떨어진 것을 볼 수 있다. 만약, 콜백 함수마다 에러를 처리해줘야 하는 상황이 발생한다면, 에러가 발생한 위치를 찾기도 어려워지고 코드의 흐름을 따라가기는 더 어려워지는 것이다.

 

 

프로미스 맛보기

콜백 함수의 단점을 보완하기 위해 나온 문법이 바로 프로미스이다. 프로미스에 대해 본격적으로 들어가기 전에, 프로미스를 사용한다면 위의 콜백지옥 코드가 어떻게 변화하는지 한번 맛보자.

function increaseAndPrint(n) {
  return new Promise((resolve, reject)=>{
    setTimeout(() => {
      const increased = n + 1;
      console.log(increased);
      resolve(increased);
    }, 1000)
  })
}

increaseAndPrint(0)
  .then((n) => increaseAndPrint(n))
  .then((n) => increaseAndPrint(n))
  .then((n) => increaseAndPrint(n))
  .then((n) => increaseAndPrint(n));

 

아직 프로미스가 무엇인지는 모르지만, 코드가 훨씬 간결해지고 가독성이 좋아진 것을 볼 수 있다. 이처럼 자바스크립트에서 프로미스는 비동기 프로그래밍의 근간이 되는 문법 중 하나이다. 이러한 프로미스가 구체적으로 무엇이며 어떻게 사용하는지 알아보자.

 

 

 

프로미스 객체란

자바스크립트의 프로미스(Promise) 객체는 비동기 작업의 최종 완료 또는 실패를 나타내는 Array나  Object 처럼 독자적인 객체이다. 비동기 작업이 끝날 때까지 결과를 기다리는 것이 아니라, 결과를 제공하겠다는 '약속'을 반환한다는 의미에서 Promise(약속)라고 지여졌다고 한다.

 

위의 설명에서 Promise 객체는 약속을 반환한다고 하였는데 이 말을 잘 살펴보면 Promise 객체는 Promise(약속)를 반환한다고 해석을 할 수 있다. 즉, Promise는 성공과 실패의 결과로 또 다시 Promise를 반환하기 때문에 반환된 Promise 객체를 이용하여 계속해서 작업을 이어 나갈 수 있다. 

 

이러한 기법을 프로미스 체이닝(promise chaining)이라고 하는데, 이는 이후 포스팅에서 더 자세히 다루겠다.

 

 

 

프로미스 객체 생성

프로미스 객체는 new 키워드와 Promise 생성자 함수를 사용하여 생성할 수 있다. 이 때, 프로미스 생성자 안에 두개의 매개변수를 가진 콜백 함수(executor)를 넣게 되는데, 첫번째 인수는 작업이 성공했을 때 성공(resolve)임을 알려주는 객체이며, 두번째 인수는 작업이 실패했을 때 실패(reject)임을 알려주는 오류 객체이다.

const myPromise = new Promise((resolve, reject) => {
	// 비동기 작업 수행
    const data = fetch('서버로부터 요청할 URL');
    
    if(data)
    	resolve(data); // 만일 요청이 성공하여 데이터가 있다면
    else
    	reject("Error"); // 만일 요청이 실패하여 데이터가 없다면
})

 

위의 코드에서 fetch는 특정 url을 서버로 부터 요청하는 비동기 함수로 만약 해당 요청의 결과가 성공이면 resolve 메소드를 호출하고 실패하면 reject 메소드를 호출하게 되는 것이다.

 

 

 

프로미스 객체 처리

이렇게 만들어진 Promise 객체는 비동기 작업이 완료된 이후에 다음 작업을 연결시켜 진행할 수 있다. 작업 결과 따라 .then() 과.catch() 메서드 체이닝을 통해 성공과 실패에 대한 후속 처리를 진행할 수 있다.

 

만일 처리가 성공하여 프로미스 객체 내부에서 resolve(date)를 호출하게 되면, 바로 .then()으로 이어져 then() 메서드의 콜백 함수에서 성공에 대한 추가 처리를 진행한다. 이때 호출한 resolve() 함수의 매개변수의 값이 then() 메서드의 콜백 함수 인자로 들어가 then() 메서드 내부에서 프로미스 객체 내부에서 다룬 값을 사용할 수 있게 된다.

 

반대로 처리가 실패하여 프로미스 객체 내부에서 reject('Error') 를 호출하게 되면, 바로 .catch() 로 이어져 catch() 메서드의 콜백 함수에서 실패에 대한 추가 처리를 진행한다. 

myPromise
    .then((value) => { // 성공적으로 수행했을 때 실행될 코드
    	console.log("Data: ", value); // 위에서 return resolve(data)의 data값이 출력된다
    })
    .catch((error) => { // 실패했을 때 실행될 코드
     	console.error(error); // 위에서 return reject("Error")의 "Error"가 출력된다
    })
    .finally(() => { // 성공하든 실패하든 무조건 실행될 코드
    	console.log('end!!');
    })

 

 

 

프로미스 함수 등록

프로미스 함수란, 프로미스 객체를 반환하는 함수이다. 위와 같이 프로미스 객체를 변수에 바로 할당하는 방식을 사용할 수 도 있지만, 보통은 프로미스 함수(프로미스 팩토리 함수)를 사용한다.

// 프로미스 객체를 반환하는 함수 생성
function myPromise() {
  return new Promise((resolve, reject) => {
    if (/* 성공 조건 */) {
      resolve(/* 결과 값 */);
    } else {
      reject(/* 에러 값 */);
    }
  });
}

// 프로미스 객체를 반환하는 함수 사용
myPromise()
    .then((result) => {
      // 성공 시 실행할 콜백 함수
    })
    .catch((error) => {
      // 실패 시 실행할 콜백 함수
    });

 

함수를 만들고 그 함수를 호출하면 프로미스 생성자를 return 함으로서, 곧바로 생성된 프로미스 객체를 함수 반환값으로 얻어 사용하는 기법이다. 이렇게 프로미스 객체를 함수로 만드는 이유는 다음 3가지가 있다.

장점 설명
재사용성 프로미스 객체를 함수로 만들면 필요할 때마다 호출하여 사용함으로써, 반복되는 비동기 작업을 효율적으로 처리할 수 있다.
가독성 프로미스 객체를 함수로 만들면 코드의 구조가 명확져, 비동기 작업의 정의와 사용을 분리하여 코드의 가독성을 높일 수 있다.
확장성 프로미스 객체를 함수로 만들면 인자를 전달하여 동적으로 비동기 작업을 수행할 수 있다. 또한 여러 개의 프로미스 객체를 반환하는 함수들을 연결하여 복잡한 비동기 로직을 구현할 수 있다.

 

이러한 장점 때문에 일반적으로 프로미스 객체를 사용할 일이 있다면, 프로미스 함수를 사용한다. 그래서 대부분의 자바스크립트 비동기 라이브러리도 함수 형태로 프로미스 객체를 제공한다. 대표적으로 자바스크립트의 fetch 메소드가 있는데, 이 fetch() 메서드 내에서 프로미스 객체를 생성하여 서버로부터 데이터를 가져오면 resolve하여 .then() 으로 처리하기 때문이다.

// GET 요청 예시
fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then((response) => response.json()) // 응답 객체에서 JSON 데이터를 추출한다.
  .then((data) => console.log(data)); // JSON 데이터를 콘솔에 출력한다.

 

 

 

 

 

reference : Inpa dev

728x90