Promise
프로미스는 후에 실행할 작업을 등록할 수 있다. 콜백함수와 동일한 구조를 가지고 있다.1 그도 그럴것이 콜백헬을 대처하기 위해 제안된 기능이다. 이해를 위해서는 콜백에 대해 먼저 짚고 넘어가야 한다.
콜백함수
const asyncFn = (callback) => {
// 5초 후 원하는 작업이 완료되면 콜백함수 호출
setTimeout(() => callback(10), 5 * 1000);
}
// 비동기 함수를 호출하면서 완료 이후에 작동할 함수를 미리 제공
asyncFn((result) => {
console.info(`Async Function Has Returned. ${result}`);
})
비동기 동작의 시점 문제를 해결하기 위한 방법으로 작업이 완료된 이후에 동작할 함수를 미리 제공하는 방법이다.
콜백헬
asyncFn(() => {
asyncFn(() => {
asyncFn(()=> {
asyncFn(()=> {
// ...
})
})
})
})
비동기 작업을 합리적으로 처리할 수 있는 방안이지만 비동기 작업 이후에 다시 비동기 작업을 해야하는 경우 등 콜백함수를 활용하는 구간이 많아지면 가독성이 아주 떨어져 유지보수가 힘들어진다.
위 예시는 원리만 표현해서 그렇지 여러 작업들이 끼어들고 에러처리를 하는 등 점점 그 모양새가 맛이 가기 시작하면 처다보기도 싫어진다. 프로세스 순서를 쫓아가는건 기본이고 내부에서 다루는 데이터 구조가 트리처럼 조금이라도 복잡하다면 손을 놓기 십상이다. 이름에서 볼 수 있듯이 말 그대로 지옥을 경험할 수 있다.
어디 그뿐이겠는가? 스코프에 대한 이해도 필요하고 클로저나 호이스팅 같은 현상도 고려해야 한다. 사실상 유지보수는 못 한다고 봐야하며 몇 개월 후에 그냥 싹다 뒤집어 엎는 수순을 밟는게 일반적이다.
const asyncFn = () => new Promise((resolve, reject) => {
setTimeout(() => resolve(Math.trunc((Math.random() * 500))), Math.trunc((Math.random() * 10000)))
});
asyncFn()
.then((res) => asyncFn())
.then((res) => asyncFn())
.then((res) => asyncFn())
.then((res) => asyncFn())
.then((res) => asyncFn())
.catch(err => console.error(err))
.finally(() => console.info("run finally"))
callback을 사용하던 함수의 몸체를 Promise의 인자로 만들어 넘겨주고 callback 함수를 호출하는 대신 Promise가 지원하는 resolve()
함수로 전달하도록 변경하였다. 함수의 반환값은 new Promise()
를 통해 생성된 Promise 인스턴스가 되도록 변경했다.
Promise를 사용하면 기존의 콜백헬을 직렬화 할 수 있다. callback 함수를 호출하는 대신 Promise가 지원해주는 resolve 함수로 반환값을 리턴하면 .then()
콜백에서 사용할 수 있다. 중간에 에러가 발생하여 throw 되거나 reject()
함수로 에러를 전달하면 .then()
의 유무와 개수에 관게없이 .catch()
로 작업이 전달된다. finally()
는 오류 유무와 관계없이 마지막에 한 번 실행된다. .then()
함수에서 다시 Promise 인스턴스를 반환하면 비동기처리가 완료되고 난 후 다시 .then()
으로 처리할 수도 있다.
asyncFn()
.then((res) => {
asyncFn()
.then((res) => {
asyncFn()
.then( /* ... */ )
})
})
좀 웃긴 예제이긴 한데 오류없이 동작한다. 실무를 하다보면 프로미스를 활용하면서 콜백을 통해 문법적 우회를 시도하는 일이 생기고는 한다. 그러면 위 같은 어처구니 없는 경우도 분명 발생한다. 이럴때는 이렇게 하면 되네? 가 아니라 뭔가 처음부터 잘못되었나? 를 고려해보자. 콜백헬을 굳이 프로미스까지 이어올 필요는 없다. Promise를 올바르게 사용하도록 하자.
Footnotes
-
nodejs의 내장객체인 util 모듈을 보면 이런 콜백함수를 프로미스로 변환해주는 promisify와 같은 함수가 제공된다. ↩