Original article: JavaScript Promise Tutorial – How to Resolve or Reject Promises in JS

프로미스는 자바스크립트에서 비동기 처리를 위한 중요한 요소이지만 프로미스를 이해하고 적용하는 것은 그렇게 쉽지 않다고 생각하셨을 수도 있습니다. 하지만 절 따라오세요, 여러분은 혼자가 아닙니다!

프로미스는 프로미스를 몇 년 동안 다뤄본 많은 웹 개발자들에게도 어려운 주제입니다.

이 글에서는 제가 지난 몇 년간 자바스크립트의 프로미스에 대해 배운 내용을 공유하면서 이러한 인식을 한번 깨보겠습니다. 도움이 되었으면 좋겠습니다.

자바스크립트의 프로미스란

프로미스는 자바스크립트의 특수 객체입니다. 프로미스는 비동기 작업이 성공적으로 수행되면 값을 생성하고, 시간 초과나 네트워크 오류 등으로 인해 실패하면 에러를 생성합니다.

성공적인 이행은 resolve 함수 호출, 그리고 에러는 reject 함수 호출을 통해 확인할 수 있습니다.

프로미스는 다음과 같이 생성자를 통해 만들 수 있습니다.

let promise = new Promise(function(resolve, reject) {    
    // 비동기 호출 후 resolve 혹은 reject 하는 곳
});

대부분의 경우 프로미스는 비동기 처리에 사용됩니다. 하지만 엄밀히 말하면 동기와 비동기 작업을 모두 이행/거절할 수 있습니다.

잠시만요, 비동기 작업을 위한 콜백 함수가 있지 않나요?

네! 맞아요. 자바스크립트에는 콜백 함수가 있습니다. 하지만 콜백은 자바스크립트에서 특별한 개념은 아닙니다. 비동기 호출이 (성공 또는 에러와 함께) 완료되면 결과를 반환하는 일반 함수입니다.

비동기란 지금 당장이 아니라 미래에 발생한다는 의미입니다. 콜백은 주로 네트워크 통신, 업로드/다운로드, 데이터베이스 조회 등과 같은 상황에서 사용됩니다.

콜백은 유용하긴 하지만 엄청난 단점도 가지고 있습니다. 때로 다른 콜백에 포함된 콜백 내부에 또 다른 콜백이 있을 수도 있습니다. 진심으로요! 이 "콜백 헬"을 예시와 함께 이해해봅시다.

콜백 헬을 피하는 방법 - PizzaHub 예시

PizzaHub에서 🍕 야채 마르게리타 피자를 주문한다고 해봅시다. 주문을 넣으면 PizzaHub은 자동적으로 우리의 위치를 파악하고 근처의 피자 가게를 찾은 뒤 우리가 요청한 피자가 주문 가능한지 찾아볼 것입니다.

주문이 가능하다면 피자와 함께 무료로 받을 수 있는 음료를 파악하고 마침내 주문을 넣게 됩니다.

주문이 성공적으로 완료되면 확인 문자를 받게 됩니다.

이 과정을 콜백 함수를 사용해 코드로 표현하면 어떨까요? 다음과 같이 생각해 봤습니다.

function orderPizza(type, name) {

    // pizzahub에 문의하기
    query(`/api/pizzahub/`, function(result, error){
       if (!error) {
           let shopId = result.shopId;

           // 가게에 피자 문의하기
           query(`/api/pizzahub/pizza/${shopid}`, function(result, error){
               if (!error) {
                   let pizzas = result.pizzas;

                   // 피자 가능 여부 확인
                   let myPizza = pizzas.find((pizza) => {
                       return (pizza.type===type && pizza.name===name);
                   });

                   // 무료 음료 확인
                   query(`/api/pizzahub/beverages/${myPizza.id}`, function(result, error){
                       if (!error) {
                           let beverage = result.id;

                           // 주문 준비
                           query(`/api/order`, {'type': type, 'name': name, 'beverage': beverage}, function(result, error){
                              if (!error) {
                                  console.log(`Your order of ${type} ${name} with ${beverage} has been placed`);
                              } else {
                                  console.log(`Bad luck, No Pizza for you today!`);
                              }
                           });

                       }
                   })
               }
           });
       } 
    });
}

// orderPizza 메서드 호출
orderPizza('veg', 'margherita');

위의 orderPizza 함수를 잘 살펴봅시다.

이 함수는 근처 피자 가게의 id를 찾기 위해 API를 호출합니다. 그리고 그 가게에서 주문 가능한 피자의 리스트를 받습니다. 그 중에 주문한 피자가 있는지 확인하고 그 피자와 함께 오는 음료를 확인하기 위해 또 다른 API 호출을 하게 됩니다. 최종적으로 주문 API를 통해 주문을 넣게 됩니다.

여기서 모든 API에 콜백을 사용하고 있습니다. 이 때문에 계속해서 콜백 안에 콜백을 넣는 방식으로 코드를 작성하게 됩니다.

이렇게 해서 우리는 콜백 지옥에 빠지게 됩니다. 누가 이런 코드를 원할까요? 게다가 가독성이 좋지 않고 에러가 발생하기도 쉬운 코드 피라미드가 생겨나게 됩니다.

콜백 지옥의 예시 모양

콜백 지옥 및 멸망의 피라미드 예시

콜백 지옥을 빠져나오거나 들어가지 않을 수 있는 몇 가지 방법들이 있습니다. 가장 흔한 방법은 프로미스비동기 함수를 사용하는 것입니다. 하지만 async 함수를 잘 이해하려면 프로미스를 먼저 잘 이해해야 합니다.

그럼 프로미스에 대해서 시작해 봅시다.

프로미스의 상태 이해하기

복습해보자면, 프로미스는 다음과 같은 생성자 문법을 통해 생성될 수 있습니다.

let promise = new Promise(function(resolve, reject) {
  // 실행할 함수
});

생성자 함수는 인자로 함수를 받습니다. 이 함수는 실행 함수라고 불립니다.

// 실행 함수는 프로미스 생성자 함수에 인자로 전달된다
function(resolve, reject) {
    // 로직이 위치할 곳
}

실행 함수는 resolvereject라는 두 가지 인자를 받습니다. 이 둘은 자바스크립트에 의해 제공되는 콜백 함수입니다. 여러분이 작성할 로직은 new Promise가 생성되면 자동적으로 호출될 실행 함수 안에 위치하게 됩니다.

프로미스를 효과적으로 사용하려면 실행 함수는 resolve 혹은 reject 콜백 함수들 중 하나를 호출해야 합니다. 이 둘에 대해선 잠시 후에 더 자세히 배워보겠습니다.

new Promise() 생성자는 프로미스 객체를 반환합니다. 실행 함수는 비동기 처리를 다루기 때문에, 반환된 프로미스 객체는 작업이 시작되거나, 완료될 때 (resolved), 그리고 에러를 반환할 때(rejected)를 알려줄 수 있어야 합니다.

프로미스 객체는 다음과 같은 내부 프로퍼티를 갖습니다.

  1. state(상태) - 이 프로퍼티는 다음과 같은 값을 갖습니다.
  • pending(대기): 실행 함수가 작업을 시작했을 때
  • fulfilled(이행): 프로미스가 이행되었을 때
  • rejected(거부): 프로미스가 거부되었을 때

프로미스의 상태

프로미스의 세 가지 상태

  1. result(결과) - 이 프로퍼티는 다음과 같은 값을 갖습니다.
  • undefined: 상태 값이 대기 중일 때
  • value: resolve(value)가 호출되었을 때
  • error: reject(error)가 호출되었을 때

이 내부 프로퍼티들은 코드로 접근할 수는 없지만 확인은 가능합니다. 즉 디버거 도구를 활용해 stateresult 프로퍼티 값을 확인할 수 있지만 직접적으로 프로그램을 사용해 접근할 수는 없습니다.

개발자 도구에서 프로미스 확인하기

프로미스의 내부 프로머티를 확인할 수 있다

프로미스의 상태는 pending, fulfilled 혹은 rejected가 될 수 있습니다. 이행(resolved) 또는 거부(rejected)된 프로미스는 settled 되었다고 표현합니다.

프로미스가 처리된 상태

처리(settled)된 프로미스는 이행(fulfilled) 혹은 거부(rejected)된 것이다

프로미스가 이행되거나 거부되는 방법

다음은 I am done이라는 값과 함께 즉시 이행되는(fulfilled 상태) 프로미스의 예시입니다.

let promise = new Promise(function(resolve, reject) {
    resolve("I am done");
});

다음은 Something is not right!이라는 에러 메세지와 함께 거절되는 (rejected 상태) 프로미스입니다.

let promise = new Promise(function(resolve, reject) {
    reject(new Error('Something is not right!'));
});

주의할 점

프로미스 실행 함수는 오직 하나의 resolve 혹은 reject 함수를 호출해야 합니다. 상태가 한번 변경되면 (pending => fulfilled 혹은 pending => rejected) 끝이기 때문입니다. 그 이후의 resolve 혹은 reject는 무시됩니다.

let promise = new Promise(function(resolve, reject) {
  resolve("I am surely going to get resolved!");

  reject(new Error('Will this be ignored?')); // 무시됨
  resolve("Ignored?"); // 무시됨
});

위의 예시에서, 오직 첫 번째 것만 이행되고 나머지 것들은 모두 무시됩니다.

프로미스를 만들고 처리하는 방법

프로미스는 (대부분 비동기적으로) 작업을 처리하기 위해 실행 함수를 사용합니다. (프로미스의 결과를 사용하는) 소비 함수는 실행 함수가 이행(성공)되거나 거부된(에러) 시점을 알아야 합니다.

.then(), .catch(), finally()와 같은 핸들러 메서드는 실행 함수와 소비 함수를 이어줌으로써 프로미스가 이행되거나 거부되었을 때 서로 동기화될 수 있도록 해줍니다.

실행 함수와 소비 함수 예시

실행 함수와 소비 함수

.then() 메서드를 사용하는 방법

.then() 메서드는 결과(resolve) 또는 에러(reject)를 다룰 때 프로미스 객체에 의해 호출됩니다.

이 메서드는 인자로 두 개의 함수를 받습니다. 보통 .then() 메서드는 프로미스의 실행 결과를 알고자 하는 소비 함수로부터 실행되어야 합니다.

promise.then(
  (result) => { 
     console.log(result);
  },
  (error) => { 
     console.log(error);
  }
);

성공했을 때의 결과만 알고 싶다면 다음과 같이 하나의 인자만 전달해줄 수도 있습니다.

promise.then(
  (result) => { 
      console.log(result);
  }
);

실패한 결과만 다루고 싶다면 다음과 같이 첫 번째 인자로 null을 전달해주면 됩니다.

promise.then(
  null,
  (error) => { 
      console.log(error)
  }
);

하지만 잠시후에 보게될 .catch() 메서드를 사용하면 더 나은 방법으로 에러를 처리할 수 있습니다.

.then().catch() 핸들러를 사용해서 결과와 에러를 처리한 몇가지 예시를 살펴봅시다. 몇 가지 실제 비동기 요청을 통해서 더 재밌게 배워보겠습니다. 포켓몬에 대한 정보를 얻고 그 결과를 프로미스를 사용해서 이행/거부하기 위해 PokeAPI를 사용하겠습니다.

먼저, PokeAPI URL을 인자로 받은 뒤 프로미스를 반환하기 위한 제네릭 함수를 만듭니다. API 호출이 성공적으로 이루어지면 이행된 프로미스가 반환됩니다. 거부된 프로미스는 어떠한 종류의 에러로부터 반환될 수 있습니다.

이제부터 프로미스를 만들어 작업하기 위해 여러 예시에서 이 함수를 사용하겠습니다.

function getPromise(URL) {
  let promise = new Promise(function (resolve, reject) {
    let req = new XMLHttpRequest();
    req.open("GET", URL);
    req.onload = function () {
      if (req.status == 200) {
        resolve(req.response);
      } else {
        reject("There is an Error!");
      }
    };
    req.send();
  });
  return promise;
}

프로미스를 위한 유틸리티 메서드

예시 1: 50가지의 포켓몬 정보 얻기

const ALL_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon?limit=50';

// 아래 함수는 위에서 언급했었죠!
let promise = getPromise(ALL_POKEMONS_URL);

const consumer = () => {
    promise.then(
        (result) => {
            console.log({result}); // 50 가지의 포켓몬 결과 출력
        },
        (error) => {
            // 유효한 URL이라면 실행되지 않는 부분
            console.log('We have encountered an Error!'); // 에러 출력
    });
}

consumer();

예시 2: 유효하지 않은 URL일 때

const POKEMONS_BAD_URL = 'https://pokeapi.co/api/v2/pokemon-bad/';

// 아래 코드는 URL의 404 에러로 인해 거절됩니다
let promise = getPromise(POKEMONS_BAD_URL);

const consumer = () => {
    promise.then(
        (result) => {
            // 프로미스는 이행(resolve)되지 않았기 때문에 이 부분은 실행되지 않습니다.
            console.log({result});
        },
        (error) => {
            // 거절된(rejected) 프로미스는 이 부분을 실행할 것입니다.
            console.log('We have encountered an Error!'); // 에러 출력
        }
    );
}

consumer();

.catch() 메서드를 사용하는 방법

프로미스의 에러(거부)를 다룰 때 이 메서드를 사용할 수 있습니다. .then()의 첫 번째 인자로 null을 전달해주는 문법은 에러를 다루기 좋은 방법은 아닙니다. 그래서 같은 작업을 위해 .catch()를 사용해 더 깔끔한 문법으로 작성할 수 있습니다.

// 유효하지 않은 URL(404 에러)로 인해 거부될 프로미스
let promise = getPromise(POKEMONS_BAD_URL);

const consumer = () => {
    promise.catch(error => console.log(error));
}

consumer();

프로미스 실행 함수나 핸들러에서 reject 함수를 실행하는 대신 new Error("Something wrong!")과 같은 에러를 던지게 되면 이 프로미스는 거부된 것으로 해석될 것입니다. 즉 .catch 핸들러 메서드에 의해 에러가 잡히게 됩니다.

프로미스 실행 함수와 핸들러 함수에서 일어나는 동기적인 예외 상황에서도 같게 동작합니다.

다음은 프로미스가 거부된 것으로 처리되어 .catch 핸들러 메서드가 호출된 예시입니다.

new Promise((resolve, reject) => {
  throw new Error("Something is wrong!");// reject 함수를 실행하지 않습니다
}).catch((error) => console.log(error)); 

.finally() 메서드를 사용하는 방법

.finally() 핸들러는 로더를 멈추거나 실시간 연결을 끊는 등 정리를 위해 사용됩니다. finally 메서드는 프로미스의 성공이나 거부 여부와 상관 없이 실행됩니다. 이 메서드는 .then()이나 .catch()를 또 실행할 수 있는 다음 핸들러에게 결과 값이나 에러를 전달합니다.

세 메서드를 함께 이해할 수 있는 예시를 살펴보겠습니다.

let loading = true;
loading && console.log('Loading...');

// 프로미스 얻기
promise = getPromise(ALL_POKEMONS_URL);

promise.finally(() => {
    loading = false;
    console.log(`Promise Settled and loading is ${loading}`);
}).then((result) => {
    console.log({result});
}).catch((error) => {
    console.log(error)
});

추가 설명

  • .finally() 메서드는 로딩을 false로 설정합니다.
  • 프로미스가 이행되면 .then() 메서드가 실행됩니다. 프로미스가 에러와 함께 거부되면 .catch() 메서드가 실행됩니다. .finally()는 이행 혹은 거부와는 관계 없이 실행됩니다.

프로미스 체인이란

promise.then()은 항상 프로미스를 반환합니다. 이 프로미스는 pending이라는 상태undefined라는 결과를 갖게될 것입니다. 이를 통해 다음 프로미스에 .then 메서드를 호출할 수 있게 해줍니다.

첫 번째 .then 메서드가 반환한 결과 값은 다음 .then 메서드가 받을 수 있습니다. 두 번째 결과는 이제 세 번째 .then()으로 전달될 수 있고 이런 방식으로 계속 이어질 수 있습니다. 이 방식은 프로미스를 밑으로 전달해주는 .then 메서드 체인을 형성합니다. 이 현상은 프로미스 체인이라고 불립니다.

프로미스 체인

프로미스 체인

다음 예제를 봅시다.

let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
}).then(onePokemonURL => {
    console.log(onePokemonURL);
}).catch(error => {
    console.log('In the catch', error);
});

먼저 프로미스를 이행하고 첫번째 포켓몬을 얻기 위한 URL을 추출합니다. 그렇게 얻은 결과는 프로미스 형태로 다음 .then() 핸들러로 전달됩니다. 결과는 다음과 같습니다.

https://pokeapi.co/api/v2/pokemon/1/

.then 메서드는 다음 중 하나를 반환합니다.

  • 결과 (이미 살펴본 것이죠)
  • 새로운 프로미스

에러를 반환할 수도 있습니다.

다음 예시는 결과와 새로운 프로미스를 반환하는 .then 메서드로 이루어진 프로미스 체인을 만든 것입니다.

// 다수의 then과 catch로 이루어진 프로미스 체인
let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
}).then(onePokemonURL => {
    console.log(onePokemonURL);
    return getPromise(onePokemonURL);
}).then(pokemon => {
    console.log(JSON.parse(pokemon));
}).catch(error => {
    console.log('In the catch', error);
});

첫 번째 .then()은 추출한 URL을 결과로 반환합니다. 이 URL은 해당 URL을 인자로 받는 새로운 프로미스를 반환하는 두 번째 .then으로 전달됩니다.

이 프로미스가 이행되면 포켓몬에 대한 정보 얻을 수 있는 체인으로 전달됩니다. 결과는 다음과 같습니다.

콘솔로 확인한 프로미스 체인의 결과

프로미스 체인의 결과

에러나 프로미스 거부가 발생하면 프로미스 체인의 .catch 메서드가 실행됩니다.

중요한 점: .then을 여러번 호출한다고 프로미스 체인이 생성되는 것은 아닙니다. 다음과 같이 코드를 작성하면 버그만 발생할 것입니다.

let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
});
promise.then(onePokemonURL => {
    console.log(onePokemonURL);
    return getPromise(onePokemonURL);
});
promise.then(pokemon => {
    console.log(JSON.parse(pokemon));
});

같은 프로미스에 .then 메서드를 세 번 호출하고 있지만 프로미스를 전달하고 있지는 않습니다. 이것은 프로미스 체인과는 다릅니다. 위 예시의 결과로 에러가 나올 것입니다.

프로미스 체인을 잘못 사용했을 때 나타나는 에러

여러 개의 프로미스를 처리하는 방법

.then, .catch, .finally 같은 핸들러 메서드와는 별개로 프로미스 API에서 사용 가능한 여섯 가지의 정적 메서드가 있습니다. 첫 네 개의 메서드들은 프로미스 배열을 받아 병렬적으로 실행합니다.

  1. Promise.all
  2. Promise.any
  3. Promise.allSettled
  4. Promise.race
  5. Promise.resolve
  6. Promise.reject

하나씩 살펴봅시다.

Promise.all() 메서드

Promise.all([promises])는 인자로 프로미스의 집합(예를 들면, 배열)을 받고 병렬로 실행시킵니다.

이 메서드는 모든 프로미스가 이행(resolve)되기를 기다린 후에 프로미스의 결과를 배열로 반환합니다. 만약 프로미스 중 어떤 하나라도 거절(reject)되거나 에러로 인해 실패한다면 다른 모든 프로미스의 결과는 무시될 것입니다.

세 마리의 포켓몬에 대한 정보를 얻기 위해 세 개의 프로미스를 생성해봅시다.

const BULBASAUR_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/bulbasaur';
const RATICATE_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/raticate';
const KAKUNA_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/kakuna';

let promise_1 = getPromise(BULBASAUR_POKEMONS_URL);
let promise_2 = getPromise(RATICATE_POKEMONS_URL);
let promise_3 = getPromise(KAKUNA_POKEMONS_URL);

Promise.all() 메서드에 프로미스 배열을 전달해 사용합니다.

Promise.all([promise_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('에러 발생');
});

결과:
Promise.all() 메서드의 처리 결과

결과에서 볼 수 있듯, 모든 프로미스의 결과가 반환됩니다. 모든 프로미스를 실행시키는 시간은 프로미스가 실행되는 데 걸리는 최대 시간과 같습니다.

Promise.any() 메서드

all() 메서드와 비슷한 Promise.any([promises]) 또한 병렬로 처리할 프로미스를 배열로 받습니다. 이 메서드는 모든 프로미스가 이행(resolve)되기를 기다리지 않습니다. 프로미스 중 하나라도 settled 되면 이행이 종료됩니다.

 Promise.any([promise_1, promise_2, promise_3]).then(result => {
     console.log(JSON.parse(result));
 }).catch(error => {
     console.log('에러 발생');
 });

결과는 이행(resolve)된 프로미스 중 하나가 될 것입니다.

프로미스가 하나라도 이행된 경우의 콘솔 결과

Promise.allSettled() 메서드

Promise.allSettled([promises]) 메서드는 모든 프로미스가 settled(resolve/reject) 되기를 기다린 후에 객체로 구성된 배열을 결과로 반환합니다. 성공적으로 이행된다면 결과는 상태(fulfilled/rejected)와 결괏값을 포함할 것입니다. 거절된(rejected) 상태라면 에러에 대한 이유를 반환할 것입니다.

이행된 프로미스들의 예시를 살펴보겠습니다.

Promise.allSettled([promise_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('에러입니다!');
});

결과:
모든 프로미스가 이행된 경우의 콘솔 결과

프로미스 중 하나인 promise_1이 거부된다면 다음과 같은 결과가 나옵니다.

let promise_1 = getPromise(POKEMONS_BAD_URL);

promise_1만 거부된 경우의 콘솔 결과

Promise.race() 메서드

Promise.race([promises])는 (가장 빠른) 첫번째 프로미스가 settled 되기를 기다린 후에 그에 따른 결과/에러를 반환합니다.

Promise.race([promise_1, promise_2, promise_3]).then(result => {
    console.log(JSON.parse(result));
}).catch(error => {
    console.log('에러 발생');
});

이행된(resolved) 가장 빠른 프로미스의 결과는 다음과 같습니다.

race 메서드의 콘솔 출력 결과

Promise.resolve/reject 메서드

Promise.resolve(value)는 전달된 값과 함께 프로미스를 이행합니다. 다음과 같습니다.

let promise = new Promise(resolve => resolve(value));

Promise.reject(error)은 다음과 같이 전달된 에러와 함께 프로미스를 거부합니다.

let promise = new Promise((resolve, reject) => reject(error));

PizzaHub 예시를 프로미스로 바꿀 수 있나요?

그럼요, 한번 해봅시다. query 메서드가 프로미스를 반환한다고 가정해봅시다. 여기 query() 메서드의 예시가 있습니다. 실제로 이 메서드는 데이터베이스와 소통해서 결과를 반환합니다. 지금 경우에는 하드코딩되어 있지만 같은 역할을 수행합니다.

function query(endpoint) {
  if (endpoint === `/api/pizzahub/`) {
    return new Promise((resolve, reject) => {
      resolve({'shopId': '123'});
    })
  } else if (endpoint.indexOf('/api/pizzahub/pizza/') >=0) {
    return new Promise((resolve, reject) => {
      resolve({pizzas: [{'type': 'veg', 'name': 'margherita', 'id': '123'}]});
    })
  } else if (endpoint.indexOf('/api/pizzahub/beverages') >=0) {
    return new Promise((resolve, reject) => {
      resolve({id: '10', 'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
    })
  } else if (endpoint === `/api/order`) {
    return new Promise((resolve, reject) => {
      resolve({'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
    })
  }
}

다음은 콜백 지옥을 리팩토링 해보겠습니다. 이를 위해 먼저 몇개의 논리적인 함수를 만들겠습니다.

// 가게 id 반환
let getShopId = result => result.shopId;

// 가게의 피자 리스트가 담긴 프로미스 반환
let getPizzaList = shopId => {
  const url = `/api/pizzahub/pizza/${shopId}`;
  return query(url);
}

// 고객의 요청에 맞는 피자를 프로미스로 반환
let getMyPizza = (result, type, name) => {
  let pizzas = result.pizzas;
  let myPizza = pizzas.find((pizza) => {
    return (pizza.type===type && pizza.name===name);
  });
  const url = `/api/pizzahub/beverages/${myPizza.id}`;
  return query(url);
}

// 주문 후 프로미스 반환
let performOrder = result => {
  let beverage = result.id;
   return query(`/api/order`, {'type': result.type, 'name': result.name, 'beverage': result.beverage});
}

// 주문 확인
let confirmOrder = result => {
    console.log(`Your order of ${result.type} ${result.name} with ${result.beverage} has been placed!`);
}

필요한 프로미스를 만들기 위해 위 함수들을 사용합니다. 여기서 콜백 지옥 예제와 비교해볼 수 있습니다. 아래 코드는 확실히 깔끔하고 우아하네요.

function orderPizza(type, name) {
  query(`/api/pizzahub/`)
  .then(result => getShopId(result))
  .then(shopId => getPizzaList(shopId))
  .then(result => getMyPizza(result, type, name))
  .then(result => performOrder(result))
  .then(result => confirmOrder(result))
  .catch(function(error){
    console.log(`Bad luck, No Pizza for you today!`);
  })
}

마지막으로 다음과 같이 피자 종류와 이름을 전달해서 orderPizza() 메서드를 호출합니다.

orderPizza('veg', 'margherita');

다음으로 배울 내용

여기까지 거의 읽으셨다면 축하드립니다! 이제 여러분은 자바스크립트의 프로미스에 대해 더 잘 이해할 수 있으실 겁니다. 이 글에서 사용된 모든 예제는 이 깃허브 레포지토리에 있습니다.

다음으로는 더 간단하게 코드를 작성할 수 있는 자바스크립트의 async 함수를 배울 차례입니다. 자바스크립트의 프로미스를 학습할 수 있는 가장 좋은 방법은 작은 예제를 만들고 쌓아가는 것입니다.

Angular, React, Vue 등과 같은 프레임워크나 라이브러리 사용과는 관계 없이 비동기 처리는 필수적입니다. 즉, 일을 더 잘 처리하기 위해선 프로미스를 이해해야 합니다.

또한 이제 여러분은 fetch 메서드가 더 쉽게 느껴지실 겁니다.

fetch('/api/user.json')
.then(function(response) {
    return response.json();
})
.then(function(json) {
    console.log(json); // {"name": "tapas", "blog": "freeCodeCamp"}
});
  • fetch 메서드는 프로미스를 반환합니다. 그래서 여기에 .then 핸들러를 호출할 수 있습니다.
  • 나머지는 우리가 글에서 배웠던 프로미스 체인에 관한 내용입니다.

마치기 전에

지금까지 읽어주셔서 감사합니다. 트위터(@tapasadhikary)에서 소통해요.

좋아하실 만한 글들을 추천드릴게요.

여기까지입니다. 다음 글에서 만나요. 그때까지 모두 잘 지내세요.