JavaScript 6분 읽기

프로미스

자바스크립트에서 비동기 처리에 관한 글을 저번 포스트에서 남겼다. 그 글에서 비동기 처리를 하면서 만날 수 있는 문제에 대해 설명했는데, 그렇다면 그 문제를 해결하는 방법이 무엇이 있는지 확인해보자.

자바스크립트에서 비동기 처리에 관한 글을 저번 포스트에서 남겼다. 그 글에서 비동기 처리를 하면서 만날 수 있는 문제에 대해 설명했는데, 그렇다면 그 문제를 해결하는 방법이 무엇이 있는지 확인해보자.

콜백

자바스크립트에서는 비동기를 지원하는 함수들이 있다. 대표적인 경우가 setTimeout이다. setTimeout은 비동기 처리를 콜백을 이용해서 해결한다. 일정 시간이 지나면 콜백함수의 결과를 확인할 수 있는 것이다. 단, 이 함수 역시 비동기로 처리되기 때문에 콜스택이 비었을 때 실행된다는 것을 알아두자.

setTimeout(() => console.log('time'),0) //0초 뒤에 콜백함수를 실행한다.

console.log('sync');

// 'sync'
// 'time'

위 함수를 보면 0초 뒤에 콜백을 실행하게 코드를 작성했지만, 결과는 콜백함수가 즉시 실행되는 것이 아니라 아래 console.log가 실행되고 난 후 실행된다. 이미 말했듯이 콜스택에는 console.log('sync')가 먼저 올라가서 실행되고 그 후에 콜스택이 비어있게 되므로 콜백함수가 콜스택으로 올라가게 된다.

실제로는 어떻게 동작할까? setTimeout을 코드 실행 도중 만나게 되면 우선 콜스택에 올라간다. 이후 바로 스택에서 나오면서 백그라운드에 타이머와 콜백을 넘겨주게 된다. 백그라운드에서 일정 시간 뒤에 콜백 함수를 테스크 큐에 넘겨주게 되고, 콜 스택에 모든 함수가 빠져나가게 되면, 테스크 큐에 있던 콜백함수가 콜 스택으로 들어가게 된다.

이런 방식으로 비동기 처리가 이루어진다. 그렇기 때문에 코드 흐름에 맞게 비동기 코드를 처리하려면 다른 방법이 필요하다. 대표적인 방법은 콜백함수를 사용하는 방법이다.

const data = [1,2,3]
const pushData = (el,callback) => {
  data.push(el);
  callback();
}
setTimeout(() => pushData(4,() => console.log(data)),0);
console.log(data);

//[1,2,3]
// undefined => 첫 콘솔로그의 return값이다.
//[1,2,3,4]

위 코드를 실행하면 결과가 위와 같이 나온다. 코드를 확인해보자. 우리가 원하는 것은 일정 시간이 지나고 data에 새로운 요소를 집어 넣은 후, 변한 데이터를 확인하는 것이다. 하지만, setTimeout이후에 console.log를 하게 되면 변한 데이터가 아니라 첫 데이터의 모습이 콘솔에 출력된다. 그 이유는 위에서 말했듯 동기처리가 먼저 콜스택에서 실행되기 때문에 비동기에서 처리한 push가 실행되지 않은 상태이기 때문이다.

그럼 어떻게 확인해야 할까? 바로 비동기 처리에서 확인하는 방법밖에 없다. pushData를 보게되면 eldata에 넣고 바로 콜백함수를 실행한다. 이렇게 되면, 데이터에 새로운 요소를 추가한 이후 콜백 함수가 실행되기 때문에 비동기처리 이후 추가적인 행동을 할 수 있는 것이다.

여기선 그 행동을 console.log라고 생각하면 된다. 그래서 setTimeout을 보면 콜백함수가 내부에서 console.log(data)를 하고 있는 것을 확인할 수 있다.

위 코드에서는 콜백함수를 이용해 데이터를 추가하는 것을 연속적으로 할 수 있다.

const data = [1,2,3]
const pushData = (el,callback) => {
  data.push(el);
  callback();
}
setTimeout(() => 
           pushData(4,
                    () => pushData(5,
                             () => pushData(6,() => console.log(data)))),0);
console.log(data);

//[1,2,3]
// undefined => 첫 콘솔로그의 return값이다.
//[1,2,3,4]

위와 같은 방식으로 콜백함수를 계속 넣어주면서 비동기 처리를 순차적으로 진행할 수 있다. 하지만, 만약 이런 처리가 많아지게 되면 가독성에 좋지 않게 된다. 또한 비동기 처리가 콜백함수 내로 한정될 수 밖에 없다. 이를 해결하기 위해 프로미스가 등장했다.

프로미스

프로미스는 비동기 상황을 일급 값으로 다룰 수 있게 만들었다는 것이 중요핟. 단순히 콜백 지옥에서 벗어나기 위해서 쓰는 것이 아니라는 것이다.

const add10 = a => new Promise(resolve => resolve(a + 10));

add10(5).then(res => console.log(res))
// 15

위와 같은 방식으로 프로미스를 사용하게 되면 비동기적으로 10을 더해주는 상황을 add10이라는 변수에 담을 수 있고, 내가 원하는 시점에 인자를 주면서 비동기 상황을 수행할 수 있게 된다. 이런 관점에서 프로미스와 콜백의 차이는 정말 크다는 것을 알 수 있다.

이렇게 된다면, 비동기 상황을 함수의 인자로 취급할 수 있기 때문에 여러 비동기 상황을 조합하는 것도 가능하게 된다.

프로미스는 두 가지 콜백 함수를 인자로 전달한다. 바로 resovlerejectresolve는 비동기 상황이 정상적으로 처리가 된 경우 호출해야 하며, then메소드를 통해 resolve함수에 전달한 인자를 받을 수 있다. reject는 정상적으로 처리되지 않은 경우 호출해야 하며, catch메소드로 받을 수 있다.

then

프로미스에는 then메소드를 붙일 수 있다. 프로미스에서 resolve를 통해 보내준 값을 then에서 받아 처리를 할 수 있게 되는 것이다. 콜백과 비슷한 것이다.

catch

프로미스에서 reject한 경우 앞에 모든 then체이닝을 무시하고 catch메소드로 가게 된다. then과 마찬가지로 reject가 전달한 값을 받아 처리할 수 있다.

async await

프로미스 상황에서도 then이 계속 체이닝되면 보기 어려운 경우가 생긴다. await을 사용하게 되면 그것을 조금 나은 코드로 변경할 수 있다.