callback과 Promise

자바스크립트에서 비동기 동시성 프로그래밍을 하는 방법은 크게 두가지가 있다. 오랫동안 사용되어 온 Callback 패턴을 사용하는 방법과 Promise를 기반으로 함수를 합성하는 방법이다. Promise에서 발전된 async ~ await으로 구현하는 방법도 있다.

그렇다면 Promise와 콜백 방법의 차이점은 무엇이고, 함수형 프로그래밍과 Promise와의 조합은 어떻게 할 수 있을까? 아래 Callback 패턴과 Promise 패턴 코드를 비교해보면서 하나씩 알아가본다. 우선 add10 함수는 Callback 함수로 100ms 이후에 동작을 실행한다.

function add10(a, callback) {
  setTimeout(() => callback(a + 10), 100);
}
add10(5, res => {
	add10(res, res => {
		add10(res, res => {
			log(res);
		});
	});
}); // 300ms 후 35

위 함수를 Promise를 통해 구현하면 아래와 같다.

function add20(a) {
  return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
}
add20(5).then(add20).then(add20).then(log); // 300ms 후 65

같은 기능을 하는 함수를 Callback 함수와 Promise로 각각 구현했을 때, Callback 함수의 경우 함수 내 중첩 함수가 많아져서 Promise 구조에 비해 복잡하여 가독성이 떨어지는 것을 알 수 있다.

비동기를 값으로 만드는 Promise

위와 같이 Promise와 Callback 패턴의 차이점은 코드 구조의 차이도 있지만 이는 중요한 차이점이 아니다. 즉, 콜백지옥과 Promise 패턴을 통해 then으로 가독성 높게 코드를 구현하는 것이 중요한 것이 아니다.

Promise와 콜백함수의 가장 중요한 차이는 어떻게 결과를 꺼내어 보느냐가 아닌 비동기 상황을 일급 값으로 다룬다는 점이다. Promise는 Promise라는 클래스를 통해 만들어진 인스턴스를 반환하는데, 그 반환값은 대기, 성공, 실패를 다루는 일급 값으로 이루어져 있다. 즉, '대기되어지고 있다'는 값을 만든다는 점에서 콜백과 큰 차이를 가지는 것이다.

이 점을 정확히 떠올리면서 프로그래밍을 할 줄 알면 그에 따른 응용할 수 있는 아이디어가 많아지므로 주의 깊게 살펴볼 필요가 있다. 즉 add20 함수는 비동기 상황에 대한 값을 만들어 리턴한다는 점이 포인트인 것이다!

add10add20에 대한 결과값을 보자

var a = add10(5, res => {
	add10(res, res => {
		add10(res, res => {
			log(res);
		});
	});
});
log(a); // undefined
add10(5, _ => _); // undefined

var b = add20(5).then(add20).then(add20).then(log); // Promise {<pending>}
add20(5, _ => _); // Promise {<pending>}

add20add10 함수와 비교하여 가진 가장 큰 이점이자 차이는 위처럼 Promise에 대한 결과값을 즉시 반환한다는 점이다. 이 때문에 그 이후에 원하는 어떤 일들을 쉽게 다룰 수 있다. 즉 add10은 함수 실행이 끝난 후 어떤 것도 실행할 수 없지만 add20의 경우 실행이 완료된 후 원하는 일을 추가적으로 할 수 있게되는 것이다.

var step1 = add20(5, _ => _); // Promise {<pending>}
step1 // Promise {<resolved>: 25}
var step2 = step1.then(a => a - 5);
step2 // Promise {<resolved>: 20}
var step3 = step2.then(a => a * 10);
step3 // Promise {<resolved>: 200}

즉, 비동기로 일어난 상황에 대해 값으로 다룰 수 있고, 값으로 다룰 수 있다는 것은 즉 일급이라는 것이고, 일급이라는 것은 어떤 변수에 할당되거나, 어떤 함수에 전달되거나, 전달된 값을 가지고 yield를 이어나갈 수 있다는 것을 의미한다. 이는 매우 중요한 차이이다..! ⭐️⭐️⭐️

값으로서의 Promise 활용

Promise가 비동기 상황을 값으로 다루는 일급의 성질을 가지고 있다는 점을 활용해 다양한 것들을 할 수 있다. 효과적으로 코드를 정리하고 좋은 로직을 만드는데 있어서 필요한 좋은 함수를 많이 만들어볼 수 있다.

아래 예제코드를 보자

const go1 = (a, f) => f(a);
const add5 = a => a + 5;

log(go1(10, add5)); // 15