Promise 객체

Table of contents

Promise 객체
프로미스 객체의 필요성
프로미스 객체에 대해 이야기 하기전에 자바스크립트의 비동기 처리에 대해 다시 한 번 살펴보자.
앞서 setTimeout
함수를 이용해 작업을 비동기적으로 처리했었고, setTimeout
함수는 작성된 코드와 같이 콜백함수와 ms단위의 지연 시간을 매개변수로 입력받는 함수라고 배웠었다.
아래는 지난시간 작성했던 workA
, workB
, workC
함수를 비동기 함수로 다시 생성하고, workD
는 동기적으로 작성한 모습이다.
const workA = (value, callback) => {
setTimeout(() => {
callback(value);
}, 5000);
};
const workB = (value, callback) => {
setTimeout(() => {
callback(value);
}, 3000);
};
const workC = (value, callback) => {
setTimeout(() => {
callback(value);
}, 10000);
};
const workD = (value, callback) => {
callback(value);
};
workA("workA", (res) => {
console.log(res);
});
workB("workB", (res) => {
console.log(res);
});
workC("workC", (res) => {
console.log(res);
});
workD("workD", (res) => {
console.log(res);
});
이번에는 이 함수들을 호출할 때, 출력할 단어와, 단어를 출력하는 함수를 인수로 넘겨주었다.
위 코드의 실행결과는 다음과 같다.
// workD
// workB
// workA
// workC
콜백지옥
이 코드를 다음과 같이 바꿔보자.
workA
에서 매개변수로 받은 값에 5를 더한다.- 그 결과값을
workB
에서 전달받아 3을 뺀다. - 그 결과값을
workC
에서 전달받아 10을 더한다.
const workA = (value, callback) => {
setTimeout(() => {
callback(value + 5);
}, 5000);
};
const workB = (value, callback) => {
setTimeout(() => {
callback(value - 3);
}, 3000);
};
const workC = (value, callback) => {
setTimeout(() => {
callback(value + 10);
}, 10000);
};
const workD = (value, callback) => {
callback(value);
};
workA(10, (resA) => {
console.log(`1. ${resA}`);
workB(resA, (resB) => {
console.log(`2. ${resB}`);
workC(resB, (resC) => {
console.log(`3. ${resC}`);
});
});
});
workD("workD", (res) => {
console.log(res);
});
자바스크립트에서 비동기 함수의 결과값을 또 다른 비동기 함수에서 사용하기 위해 위와같이 콜백함수 안에 콜백함수를 전달하는 방식을 사용하게 되면 다음과 같은 장점이 있다.
- 함수의 실행 순서를 알기 쉽다.
- 유연한 프로그래밍을 할 수 있다.
하지만, 동시에 가독성이 매우 좋지 않아 많은 오류를 발생시킬 수도 있는 것이 단점이다.
위와같이 >
모양으로 복잡하게 생긴 코드를 콜백 지옥이라고 하는데, 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 과도하게 깊어지는 것을 의미한다.
이러한 콜백 지옥은 프로미스(Promise) 객체를 이용해 해결할 수 있다.
프로미스 객체
프로미스 객체는 비동기 작업을 더 편리하게 처리할 수 있게 도와주는 자바스크립트의 내장 객체이다.
생성방법
프로미스 객체는 new
키워드와 생성자를 사용해 생성할 수 있다.
const promise = new Promise();
프로미스 객체를 만들 때에는 인수로 executor
라는 실행함수를 전달하는데, 이 실행함수는 프로미스 생성자에 반드시 들어가야 하는 함수로 작업을 비동기로 처리한다.
const exeutor = (resolve, reject) => {
// 코드
}
const promise = new Promise();
아래의 코드를 실행하면 '3초만 기다리세요'라는 문장이 출력되는 것을 확인할 수 있다.
const executor = (resolve, reject) => {
setTimeout(() => {
console.log('3초만 기다리세요');
}, 3000)
}
const promise = new Promise();
이처럼 executor
함수는 프로미스 객체를 생성함과 동시에 실행되는 실행함수이다.
resolve와 reject
executor
의 매개변수인 resolve
와 reject
는 자바스크립트에서 자체적으로 제공하는 콜백함수이다.
executor
는 비동기처리가 성공하면 resolve
를, 실패한다면 reject
를 호출한다.
프로미스 내부 프로퍼티
프로미스 객체는 state
, result
이렇게 두 가지의 내부 프로퍼티를 갖는다.
프로미스 객체는 맨 처음 대기(pending)상태와 undefined의 값을 가지고 있다가 executor
가 호출하는 콜백함수에 따라 state
와 result
가 변화된다.
위 그림과같이 프로미스 객체의 상태는 resolve
와 reject
를 통해 변하는데, 한 번 변경된 상태는 더 이상 변하지 않기 때문에 처리가 끝난 프로미스 객체에 resolve
나 reject
를 호출하면 무시된다.
resolve와 reject 사용하기
resolve
const executor = (resolve, reject) => {
setTimeout(() => {
resolve('성공');
}, 3000)
}
const promise = new Promise(executor);
promise.then((res) => {
console.log(res);
})
프로미스 객체가 생성되면서 동시에 executor
함수가 실행 -> executor
함수에서는 매개변수로 받은 콜백함수 중 resolve
함수를 사용해 '성공'이라는 값을 전달
이 때, 프로미스 객체의 state
는 pending에서 fulfilled로 변경되고, result
는 undefined에서 '성공'으로 변경된다.
reject
const executor = (resolve, reject) => {
setTimeout(() => {
reject('실패');
}, 3000)
}
const promise = new Promise(executor);
promise.then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
})
reject
함수가 실행되었기 때문에 프로미스의 객체의 state
는 pending에서 rejected로, result
는 undefined에서 '실패'로 변경된다.
여기서 작업이 성공했을 때 사용되는 메서드는 then
이고, 작업이 실패했을 때의 결과값을 사용하기 위해서는 catch
메서드를 사용해야 한다.
콜백지옥을 해결하는 Promise 객체
위에서 겪었던 콜백지옥 함수를 프로미스 객체를 사용해 다시 작성해보자.
const workA = () => {};
const workB = () => {};
const workC = () => {};
const workA = (value) => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value + 5);
}, 5000)
});
return promise;
};
const workB = (value) => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value - 3);
}, 3000);
});
return promise;
};
const workC = (value) => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value + 10);
}, 10000);
});
return promise;
};
workA(10).then((resA) => {
console.log(`1. ${resA}`);
workB(resA).then((resB) => {
console.log(`2. ${resB}`);
workC(resB).then((resC) => {
console.log(`3. ${resC}`);
});
});
});
작성해 본 결과, 코드가 많이 깔끔해지긴 했지만 여전히 콜백지옥의 코드와 많이 닮아있다.
then
메서드를 다른방식으로 사용하면 위 코드를 더 깔끔하게 작성할 수 있다.
프로미스 체이닝
이번에는 then
메서드의 콜백함수 안에서 workB
를 return
해보자.
workA(10).then((resA) => {
console.log(`1. ${resA}`);
return workB(resA);
}).then((resB) => {
console.log(`2. ${resB}`);
return workC(resB);
}).then((resC) => {
console.log(`3. ${resC}`);
});
이렇게 workB
함수가 반환되게 하면, workB
함수의 반환값인 프로미스 객체가 반환되는 것이기 때문에 다시 then
을 사용할 수 있게 된다.
위와같이 계속해서 프로미스 객체를 반환하며 then
메서드를 연속으로 사용하는 것을 프로미스 체이닝 이라고 한다.
프로미스 체이닝을 사용하면 코드를 아래쪽으로 계속 작성할 수 있기 때문에 더 깔끔해지고, 직관적인 코드해석이 가능해진다.
Subscribe to my newsletter
Read articles from woodstock directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

woodstock
woodstock
안녕하세요! 프론트엔드 개발자 woodstock입니다. 저는 매일 조금씩 발전하고자 하는 마음으로 개발공부를 시작했고, 이 블로그는 그 과정에서 배우고 성장하는 이야기를 담고 있습니다. 여러분의 피드백과 조언은 언제나 환영합니다! 함께 배우고 성장하는 과정을 즐길 수 있기를 기대합니다.