Promise - 프로미스

chanto11

·

2021. 1. 1. 17:05

Promise는 JavaScirpt에서 비동기 작업을 위한 중요한 구성 요소입니다. 그리고 Promise를 이해하고 배우고 응용하기가 쉽지 않다고 생각할 수 있습니다. 

걱정하지 마세요. Promise는 대다수의 많은 웹 개발자들에게도 어려운 부분입니다.


자바스크립트에서 Promise는 무엇인가?

Promise는 자바스크립트의 특별한 객체입니다. Promise는 비동기 작업이 성공적으로 완료된 후 값을 생성하거나 time out, network error 등으로 인해 성공적으로 완료되지 않은 경우는 오류를 생성합니다.

성공 => 값(성공된),   오류 => 오류 값

 

성공적으로 작업을 완료하면 resolve 함수 호출을 가르키고 에러가 발생하면 reject 함수호출을 합니다.

 

다음과 같이 Promise 생성자를 이용하여 Promise를 만들 수 있습니다.

let promise = new Promise(function(resolve, reject) {
	// 비동기 호출 코드와 resolve나 reject 시 코드작성
});

대부분의 Promise는 비동기 동작에서 사용됩니다. 그러나 기술적으로 동기와 비동기 모두에서 resolve / reject를 사용할 수 있습니다.


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

아, 네! 맞습니다. JavaScript에는 콜백 함수가 있습니다. 그러나 콜백은 JavaScript에서 특별한 것이 아닙니다. 비동기 호출이 완료된 후 결과를 생성하는 정규 함수입니다 (성공 / 오류 포함).

'비동기'라는 단어는 지금이 아니라 미래에 어떤 일이 발생한다는 것을 의미합니다. 일반적으로 콜백은 네트워크 호출, 업로드 / 다운로드, 데이터베이스와 통신 등과 같은 작업을 수행할 때만 사용됩니다.

콜백이 도움이 되지만, 거기에도 큰 단점이 있습니다. 때때로 우리는 또 다른 콜백에 있는 또 다른 콜백 내부에 하나의 콜백이 있을 수 있습니다. "콜백 지옥"을 예로 들 수 있습니다. 

callback hell - 콜백 지옥


콜백 지옥을 어떻게 피할까? - PizzaHub 예제

1. 피자가 주문되었을 때 pizzahub에서 우리의 위치를 자동으로 위치를 확인해서 근처 피자 레스토랑을 찾은 다음 요청한 피자가 있는지 확인합니다.

2. 주문이 가능한 경우 피자와 합계 무료로 제공되는 음료의 종류를 확인하여 마지막으로 주문합니다.

3. 성공적으로 주문이 완료되면 확인 메시지가 표시됩니다. 

 

코드를 작성하여 살펴볼까요?

function orderPizza(type, name) {
    
    // Query the pizzahub for a store
    query(`/api/pizzahub/`, function(result, error){
       if (!error) {
           let shopId = result.shopId;
           
           // Get the store and query pizzas
           query(`/api/pizzahub/pizza/${shopid}`, function(result, error){
               if (!error) {
                   let pizzas = result.pizzas;
                   
                   // Find if my pizza is availavle
                   let myPizza = pizzas.find((pizza) => {
                       return (pizza.type===type && pizza.name===name);
                   });
                   
                   // Check for the free beverages
                   query(`/api/pizzahub/beverages/${myPizza.id}`, function(result, error){
                       if (!error) {
                           let beverage = result.id;
                           
                           // Prepare an order
                           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!`);
                              }
                           });

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

// Call the orderPizza method
orderPizza('veg', 'margherita');

여기서는 각 API 호출에 대해 콜백을 사용합니다. 이로 인해 이전 콜백에서 다른 콜백을 사용하게 됩니다.

이것을 우리가 callback hell이라고 부릅니다 또한 오류가 발생하기에 쉬운 코드가 됩니다.

 

콜백 지옥에서 나오거나 들어가지 않는 몇 가지 방법이 있습니다. 가장 일반적인 방법은 Promise 또는 async 함수를 사용하는 것입니다. 그러나 비동기 함수를 잘 이해하려면 먼저 Promise에 대해 공정하게 이해해야 합니다.

 

이제 Promise를 알아봅시다.


Promise 상태 이해하기

생성자 함수는 함수를 인자로 사용합니다. 이 함수를 실행 함수라고 합니다.

let promise = new Promise(
	function(resolve, reject) { // executor function (실행 함수)
  		// Code to execute
	}
);

이 실행함수는 resolve 와 reject 두개의 인자를 가지고 인자들은 자바스크립트가 제공하는 콜백입니다. 여러분이 작성한 로직은 new Promise가 생성될 때 자동으로 실행되는 실행 함수 내부에서 실행하게 됩니다.

new Promise() 생성자는 promise객체를 리턴합니다. 실행 함수가 비동기 작업을 처리해야하므로 리턴 된 promise 객체는 실행 시작, 완료(resolve)되거나 에러(reject)와 함께 리턴된 시기를 알 수 있어야 합니다.

 

promise - state

penging : 처음 실행 함수가 실행할 때 상태

fulfilled : resolve 된 상태
rejected : reject 된 상태

promise 상태

promise - result

undefined : 초기 상태의 값 (pending 상태)

value : resolve 상태의 값

error : reject 상태의 값

 

이러한 내부 속성은 코드에 액세스 할 수 없지만 검사 할 수 있습니다. 즉, 디버거 도구를 사용하여 상태 및 결과 속성 값을 검사 할 수는 있지만 프로그램을 사용하여 직접 액세스 할 수는 없습니다.

promise 상태는 pending, fulilled 이나 reject를 가지며, promise가 resolved나 rejected 가 되면 settled라고 부릅니다.


promise의 resolved 와 rejected 방법

resolved (fulfilled state)  value => "I am done"

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

rejected (rejected state)  value => 'Something is not right!'

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

Promise 실행자는 하나의 resolve 나 하나의 reject 만 호출할 수 있습니다. 한번의 상태가 변경되면(pending=>fulfilled or pending=>rejected) 이후 호출들은 무시됩니다.

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

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

Promise를 생성 후 처리하는 방법

Promise의 실행 함수가 작업(대부분 비동기 처리)을 완료하고 실행 함수가 resolve(success)나 reject(error)중 결과를 받아 올 때 consumer 함수에 알려주어야 합니다.

실행 함수와 consumer 함수

핸들러 메소드인 .then(), .catch(), 와 .filnally() 는 promise가 resolve되거나 reject될 때 실행 함수와 consumer 함수 사이가 동기화 될 수 있게 두 함수 사이에 링크를 생성해 줍니다.

 


Promise 핸들러 .then() 사용 방법

.then() 메소드는 Promise의 실행 결과를 알고자 하는 함수에서 호출되어지고 result(resolve) 와 error(reject) 두 매개변수로 각각 처리할 수 있습니다.

Promise.then(
	
    (result) => {
    	console.log(result)
    },
    (error) => {
    	console.log(error)
    }
);
promise.then(
  (result) => { 
      console.log(result);
  } // resolve 처리만 할 경우 한개의 인수로 처리
);
promise.then(
  null,  // error처리만 할 경우 null값을 줄 수 있다
  (error) => { 
      console.log(error)
  }
);

하지만 .catch() 메서드를 이용하면 더 나은 방법으로 처리 할 수있습니다.

 

PokeAPI(https://pokeapi.co/)를 이용하여 실제 비동기 요청을 하여 .then.catch 핸들러를 비교해 봅시다.

 

먼저 PokeAPI URL 인수로 Promise를 반환하는 함수를 만들어 봅시다.

 

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;
}

resolve 예제

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

// We have discussed this function already!
let promise = getPromise(ALL_POKEMONS_URL);

const consumer = () => {
    promise.then(
        (result) => {
            console.log({result}); // Log the result of 50 Pokemons
        },
        (error) => {
            // As the URL is a valid one, this will not be called.
            console.log('We have encountered an Error!'); // Log an error
    });
}

consumer();

reject 예제

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

// This will reject as the URL is 404
let promise = getPromise(POKEMONS_BAD_URL);

const consumer = () => {
    promise.then(
        (result) => {
            // The promise didn't resolve. Hence, it will
            // not be executed.
            console.log({result});
        },
        (error) => {
            // A rejected prmise will execute this
            console.log('We have encountered an Error!'); // Log an error
        }
    );
}

consumer();

 


Promise 핸들러 .catch() 사용 방법

.then 핸들러 처럼 첫번째 인자에 null값을 넣지 않아도 .catch 핸들러는 error(reject)를 처리할 수 있습니다.

.catch.then에 null을 전달하는 것과 동일하게 작동됩니다.

// This will reject as the URL is 404
let promise = getPromise(POKEMONS_BAD_URL);

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

consumer();

하지만 .catch는 Promise 실행 함수나 핸들러에서 reject를 호출하지 않고 new Error("Error code 1") 같은 에러 발생시 에도 reject 처리가 됩니다. 이것은 실행 함수나 핸들러에서 사용하는 예외처리와 동일합니다.

new Promise((resolve, reject) => {
  throw new Error("Something is wrong!");// No reject call
}).catch((error) => console.log(error)); 

 


Promise 핸들러 .finally() 사용 방법

.finally 핸들러는 Promise가 처리되면 항상 수행하고 Promise가 resolve나 reject 되는 것과 관계없이 수행됩니다. 

예를 들면 (Promise 수행) -> (로딩 시작) -> (처리) -> (결과와 상관없이 로딩 종료) 이런 처리를 할 때 쓰인다.

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

// Gatting Promise
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 에 의해 loading 이 false로 바뀌고 resolve면 .then을 호출 reject나 error는 .catch를 호출한다.


Promise Chain 은 무엇인가?

promise.then()은 항상 promise를 리턴하고 그 promise state 는 pending 이나 result 는 undefined를 가진다. 그리고 계속 .then() 으로 새로운 프로미스를 이어나갈수 있습니다.

첫번째 .then 메소드가 value를 리턴하면 다음 .then 메소드가 value를 받습니다. 그리고 세번째 .then까지도 전달할 수 있습니다. 이렇게 promise를 아래로 전달하기위해 .then 메소드는 줄줄이 연결되고 이것을 Promise Chain이라고 합니다.

Promise Chain

 


여러가지 Promise 핸들러들 - Promise API

위의 then, catch, finally 외에도 Promise API에는 6개의 정적 메소드들이 있습니다. 그리고 1~4번의 메소드는 Promise 배열과 병렬 처리가 가능합니다.

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

Promise.all 메소드

Promise.all( [promises] ) 인수로 promise collection (array...) 를 받아 병렬 처리한다.
Promise.all은 모든 promise가 resolve 될 때까지 기다렸다가 결과 배열을 리턴받습니다. promise들을 처리중 하나라도 error나 reject 될시 promise.all의 결과는 무시됩니다.

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_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('An Error Occured');
});

결과

# 모든 promise을 실행하는 데 걸리는 시간은 promise을 실행하는 데 걸리는 최대 시간과 같습니다.


Promise.any 메소드

Promise.any( [promises] ) 는 Promise.all 과 비슷합니다. 

 Promise.any([promise_1, promise_2, promise_3]).then(result => {
     console.log(JSON.parse(result));
 }).catch(error => {
     console.log('An Error Occured');
 });

결과

# promise 중 하나라도 resolve되면 promise를 반환합니다.


Promise.allSettled 메소드

Promise.allSettled( [promises] ) 는 모든 promise가 settle(resolve/reject)될 때까지 기다렸다가 결과를 객체 배열로 반환합니다. 결과에는 state(fulfilled/rejected), value 값(fulfilled의 경우만) 이 포함됩니다. rejected인 경우 error 이유를 반환합니다.

Promise.allSettled([promise_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('There is an Error!');
});

resolve 결과
reject 있는 경우 결과

 


Promise.race 메소드

Promise.race( [promises] ) 는 가장 빠른 Promise가 해결 될 때까지 기다렸다가 그에 따라 resolve / reject를 반환합니다.

Promise.race([promise_1, promise_2, promise_3]).then(result => {
    console.log(JSON.parse(result));
}).catch(error => {
    console.log('An Error Occured');
});

결과


Promise.resolve / reject 메소드

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

원문 : 

www.freecodecamp.org/news/javascript-promise-tutorial-how-to-resolve-or-reject-promises-in-js/?utm_campaign=Frontend%2BWeekly&utm_medium=email&utm_source=Frontend_Weekly_234

'Javascript' 카테고리의 다른 글

일급 객체와 고차 함수  (0) 2021.02.03
addEventListener 와 onclick  (0) 2021.01.12
reducer - 리듀서  (0) 2020.12.29
[js] Promise API 의 3가지 상태  (0) 2020.09.04
[js] 자바스크립트 Value  (0) 2020.05.23