JS Callback / Promise

2020. 8. 13. 16:29공부내용 공유하기

 

/*
In JavaScript, functions are first-class objects, 
because they can have properties and methods just like any other object.
What distinguishes them from other objects is that functions can be called. 
In brief, they are Function objects.
*/

 

JS에서 함수는 객체로 취급된다.

 

이유는 객체가 가지고 있는 속성, 기능을 모두 가질 수 있기 때문이다.

 

Callback이란 Observer 디자인 패턴에서 나온 개념으로, 객체의 상태변화(이벤트) 발생시에 이 사실을 함수로 전달하는 데 사용되었다.

java에서는 주로 인터페이스를 통해 구현한다

알게 모르게 이미 많이 사용했던 것들로, ajax에서 success, error, complete (done, fail, always) 단계에 넣는 함수들도 콜백 함수가 되시겠다.

const ajax = (ajax_url, ajax_type, ajax_data, ajax_data_type) => {
  $.ajax({
    // 요청을 보낼 URL
    url: ajax_url,
    // HTTP 요청 방식 (GET, POST)
    type: ajax_type,
    // HTTP 요청과 함께 서버로 보낼 데이터
    data: ajax_data,
    // 서버에서 보내줄 데이터의 타입 ( xml, text, html, json 등)
    dataType: ajax_data_type,
  })
    .done(function (data) {
      console.log('성공적으로 데이터 수신 : ' + data[0]);
    })
    .fail((data) => {
      console.log('실패');
    })
    .always((data) => {
      console.log('종료되었습니다.');
    });
};

 

흔히 콜백 함수를 사용하면서 '콜백 지옥'에 빠진다는 표현을 하는데, 이는 비동기 프로그래밍시에 실행 순서를 정의하는 과정에서 콜백 함수를 연결시키다가 생기는 문제이다.

 

function dosomething(x){
  x.do(x, function(x){
    x.some(x, function(x){
      x.thing(x. function(x){
        return x*3;
        })
      })
    })
}

 

보다시피 참 아름답지 않을 뿐더러 읽히지도 않는다.

 

동기화 처리를 하기 위해 이렇게 콜백 함수를 중첩해서 사용하게 되면, 계층이 복잡해지며 가독성도 떨어지게 된다.

 

물론 콜백 함수를 익명함수로 넘기지 않고 선언하고 이름을 지정해서 사용하면 좀 나아지긴 하지만, 여전히 좋은 해결책은 아니다.

 

 

이 문제를 해결하기 위해 나온 패턴이 바로 Promise이다.

 

Promise의 기본 형태는 다음과 같다.

 

new Promise(/* excutor */ function(resolve, reject) { } );

 

excutor는 기본적으로 resolve와 reject 두 인자를 가진다.

각각 정상 동작시(resolve), 에러 발생시(reject) 반환하는 promise 객체이며, excutor가 비동기 동작을 수행한 결과에 따라 다르게 반환된다.

 

Promise는 3가지의 상태를 가진다.

pending : 초기 상태 ~ 성공 또는 실패 결과가 나오기 전의 상태

fulfilled : 동작이 성공된 상태

rejected : 동작이 실패한 상태

 

 

 사용 예시는 다음과 같다.

 

var promise_example = (x) => {
  return new Promise((resolve, reject) => {
    if(x){
      resolve('true');
    }else{
      reject('false');
      };
})};

promise_example(true)
.then((success) => {
console.log(success);
}, (fail) => {
console.log(fail);
})
.catch((fail) => {
console.log('거짓');
});

 Promise가 정상적으로 처리되었을 때 실행할 일은 then에, 실패했을 경우 실행할 일은 catch에 담으면 되는데, then 안에 인자를 2개 넣어서 하나는 성공시, 하나는 실패시 실행되는 콜백을 담아도 된다.

 

위에 있는 예제를 실행하면 'true'가 출력되고, 인자를 false로 바꾸면 'false'가 출력된다.

 

then의 인자를 하나만 넣으면 .catch에 잡히면서 false시 '거짓'이 출력된다.

 

then에 인자를 2개 넣었을 때 catch에 잡히지 않는 이유는 이미 앞에서 반환된(then) 반환값이 promise객체가 아니기 때문이다.

 

p.then(onFulfilled[, onRejected]);

p.then(function(value) { // fulfillment }, function(reason) { // rejection });

 

promise들을 then과 catch를 통해서 chaning이 가능하고, chaning을 통해 flow를 쉽게(?)구성 할 수 있다.

 

사실 promise의 핵심은 전용 method를 통해 다수의 promise의 상태값을 파악하고, 그에 따른 처리가 가능하다는 점이다.

 

promise의 활용성을 극대화할 전역 메소드들이 있다.

 

all : 모든 promise가 fullfilled된 경우 실행. 예외처리는 역시 catch로 가능하다. 전반적인 실행 상황을 파악 할 수 있다.

race : 처음으로 종료된 promise를 반환한다. 말 그대로 경주(race)를 통해 이긴놈만 쟁취한다.

reject : reject된 promise 객체를 강제로 반환한다. 

resolve : resolve된 promise 객체를 강제로 반환한다.