자바스크립트 Promise 개념과 사용하는 이유

이동준·2023년 7월 24일
0

자바스크립트

목록 보기
11/28

자바스크립트에서 비동기 처리란, 현재 실행중인 작업과는 별도로 다른 작업을 수행하는 것을 말하는데, 예를 들어 서버에서 데이터를 받아오는 작업은 시간이 걸리기 때문에 자바스크립트의 서버 호출 함수는 비동기 함수로 이루어져 있다.

비동기는 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 방식이기 때문에 비동기 작업의 결과에 따라 다른 작업을 수행해야 할 때는 전통적으로 콜백 함수를 사용해왔다.

콜백 지옥

콜백 함수란, 비동기 작업이 완료되면 호출되는 함수의 의미로서 비동기 함수의 매개변수로 함수 객체를 넘기는 기법을 말한다. 그래서 함수 내부에서 함수 호출을 통해 비동기 작업의 결과를 받아서 인자로 주면 이를 통해 후속 처리 작업이 가능하다.
하지만 콜백 함수를 사용하면 코드가 복잡해지고 가독성이 매우 떨어지게 된다. 특히 여러개의 비동기 작업을 순차적으로 수행해야 할 때는 콜백 함수가 중첩되어 코드의 깊이가 깊어지는 이상한 현상이 발생한다. 이러한 현상을 콜백 지옥(callback hell)이라고 부른다.

콜백 함수 비동기 처리 문법

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('끝!');
        });
      });
    });
  });
});

숫자 n 을 매개 변수로 받아와 다섯번에 걸쳐 1초마다 1씩 더해서 출력하는 작업을 setTimeout() 비동기 함수로 구현한 코드이다. 보기 매우 안좋게 휘어있는 걸 볼 수 있다.
이러한 콜백 함수의 코드 형태는 콜백 함수가 중첩되면서 들여쓰기 수준이 깊어져 코드의 가독성을 떨어뜨리고, 코드의 흐름을 파악하기 어려워진다. 또한 콜백 함수마다 에러 처리를 따로 해줘야하는 번거로움이 존재하고, 에러가 발생한 위치를 추적하기 힘들다.

프로미스로 개선된 비동기 처리 문법

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 객체를 이용해 리팩토링하면, 비동기 작업의 개숙가 많아지더라도 들여쓰기 코드의 깊이가 깊어지지 않게 된다. 프로미스 문법에 대해 잘 몰라도 .then() 을 통해 이를 열거하여 비동기 처리 결과가 깔끔하게 표현되었음을 알 수 있다.
자바스크립트 프로미스는 비동기 프로그래밍의 근간이 되는 기법 중 하나이다. 프로미스를 사용하면 콜백 함수를 완벽히 대체할 수 있고, 비동기 작업의 흐름을 쉽게 제어할 수 있다.

프로미스 객체 사용법

프로미스 객체 생성

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

프로미스 생성자 안에 들어가는 콜백 함수를 executor 라고 부른다.

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

위 코드는 서버로부터 요청하는 비동기 작업이 성공하냐 실패하냐에 따라 매개변수를 호출하는 것이 나뉘는 것을 확인 할 수 있다.
만약 작업이 성공한다면 비동기 로직 실행이 참이라는 것을 알려주기 위해 resolve() 메소드를 호출, 실패한다면 reject() 메소드를 호출한다.

프로미스 객체 처리

그렇게 만들어진 프로미스 객체는 비동기 작업이 완료된 이후에 다음 작업을 연결시켜 진행 할 수 있다. 작업 결과에 따라 .then().catch() 메소드 체이닝을 통해 성공과 실패에 대한 후속 처리를 진행 할 수 있다.
처리가 성공하여 프로미스 객체 내부에서 resolve() 를 호출하게 되면, 바로 .then() 으로 이어져 콜백 함수에서 성공에 대한 추가 처리를 진행한다.
이때 호출한 resolve() 함수의 매개 변수 값이 then() 메소드의 콜백 함수 인자로 들어가 then() 메소드 내부에서 프로미스 객체 내부에서 다룬 값을 사용할 수 있게 된다.
처리가 실패하여 프로미스 객체 내부에서 reject() 를 호출하게 되면, 바로 .catch() 메소드로 이어져 콜백 함수에서 성공에 대한 추가 처리를 하게된다.

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

프로미스 함수 등록

위와 같이 프로미스 객체를 변수에 바로 할당하는 방식을 사용 할 수도 있지만, 일반적인 상황은 다음과 같이 별도의 함수로 감싸서 사용하는 방식이다.

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

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

함수를 만들고 그 함수를 호출하면 프로미스 생성자를 리턴 함으로써, 생성된 프로미스 객체를 함수 반환값으로 받아 사용하는 기법이다. 이렇게 프로미스 객체를 함수로 만드는 이유는 3가지 정도가 있다.

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

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

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

요약

  1. 프로미스는 콜백 지옥을 리팩토링 할 수 있는 신세대 문법이다.
  2. 프로미스의 사용 방법으로는 프로미스 객체 생성, 객체 처리, 함수 등록이 있다.
  3. 결국 또 async/await 가 필요하다.

0개의 댓글