프로미스(Promise)란?
자바스크립트에서 비동기 처리를 동기로 처리할 수 있게 돕는 Built-in(미리 내부적으로 정의된) 객체 유형입니다.
이 객체를 이용하면 여러분은 비동기 처리를 아주 손쉽게 할 수 있습니다.
비동기 처리를 왜 동기적으로 처리해야 하나요?
정확히 말하자면 Javascript 관점에서 비동기적인 코드를 동기적인 것처럼 처리한다는 것입니다.
왜 이런 동작이 필요할까요? 이전에 setTimeout 비동기 함수 처리 예제를 통해 비동기 함수보다 동기 명령이 우선적으로 처리되는 상황을 살펴보았습니다. 예제에서 살펴본 내용은 지극히 합리적입니다. 왜냐면 '그렇게 의도'했기 때문입니다.
first를 출력하는 것이 Middle과 Last보다 늦는 것이 의도적이라는 것입니다. 그러나 First - Middle - Last를 순서대로 출력하고 싶다면 상황이 달라집니다. 이 때는 First가 출력될 때까지 얼마가 걸리던 Middle과 Last는 출력되어선 안됩니다. 이럴 때 필요한 것이 동기적인 것처럼 출력하는 방법입니다.
첫 번째로 내릴 수 있는 결론은, 비동기적 진행의 선택은 개발자의 의도에 따라 결정된다는 것입니다.
조금 더 현실적인 예를 들어보겠습니다. 데이터베이스의 데이터를 먼저 가져온 후, 그 데이터를 가공하여 반환하는 함수가 존재한다고 가정해보겠습니다.
데이터베이스와의 소통은 I/O이고, Javascript에서 거의 모든 I/O는 비동기적이므로 아마도 데이터를 가져오기도 전에 데이터 가공 명령이 실행돼버리고 말 것입니다(에러 발생).
이런 상황에서 매우 유용하게 쓰일 수 있는 방법이 Promise입니다. 단어 그대로 '언제 진행할지 약속'한다고 생각하시면 좋습니다. 언제 진행할지란, 바로 비동기 명령의 실행이 완료된 이후를 말하는 것입니다.
데이터베이스의 예로 돌아가서, 데이터를 가져온 이후 데이터를 처리하는 명령어들을 Promise 이후에 진행하도록 작성한다면 데이터를 가져오지도 않았는데 처리가 시작되는 오류 상황을 회피할 수 있을 것입니다.
참고로, Promise를 떠난다면 전통적으로 이런 비동기적인 상황을 회피하는 조금 더 일반적인 감각은 callback이라는 방식입니다. 정확히 말하면, Promise도 callback으로 작동하고 있습니다.
Javascript 함수(function)의 특별한 성질
Javascript 개발자인 우리들은 callback을 사용하고자 할 때, 어떤 함수의 인자(argument)로 또 다른 함수를 넘기고 있습니다. 우리는 이런 동작이 얼마나 특별한 일인지 잘 느끼지 못하는데, 이게 왜 이상한 것인지 차근차근 알아가 보겠습니다.
프로그래밍 언어의 모든 구성 요소는 크게 두 가지로 구분됩니다. 바로 표현(Expression)과 문(Statement)입니다. 문은 도 다른 문을 가질 가능성이 있는데, 이 가능성에 따라 또 다른 문을 가진다면 '복합문', 다른 문을 가지지 않는다면 '단일문'이라고 부릅니다.
if문과 for문에는 또 다른 문을 포함할 수 있으니 복합문이고, return은 다른 문을 가질 수 없으니 단일 문입니다.
그럼 표현이란 무엇일까요? 말 그대로 어떤 값을 표현하는 것이라고 이해하면 됩니다. 예를 들어, 0+1은 1을 표현합니다. 'sparta'는 sparta라는 문자열을 표현하고 있습니다.
그렇다면 도대체 function은 왜 특별하다는 것일까요?
function은 복합문입니다. 그렇다면 function문과 if문 등 다른 문들은 무슨 차이가 있을까요? 예시를 살펴보겠습니다.
let forStatement = for (...) {...} // Error!
let ifStatement = if (...) {...} // Error!
let functionStatement = function (...) {...} // OK?!
뭔가 특별한 일이 벌어졌습니다. 원래 문은 '값'이 아니기 때문에 어떤 변수에 할당할 수 없고 식별자로 표시할 수도 없습니다. 그런데 function문은 변수에 할당해도 오류가 없습니다. 이런 동작이 가능한 이유는 평범한 문의 개념과는 다른 게 Javascript에서 함수는 객체, 즉 '표현'취급이 가능하기 때문입니다.
이 때문에 우리는 어떤 함수를 호출할 때, 그 인자로 다른 함수를 넘기는 특별한 동작이 가능한 것입니다(인자에 if문이나 for문은 넘길 수 없습니다). 이는 callback을 자유롭게 작성해 활용할 수 있는 근거가 됩니다. 앞으로 우리는 함수를 사용할 때 함수를 표현으로 사용하는 상황을 매우 자주 목격하게 될 것입니다.
참고로, 다른 언어에서도 함수를 값으로 취급하는 문법은 Javascript와 완전히 같지는 않더라도 꽤 지원됩니다. 함수를 값으로 취급할 수 있다는 것은 높은 직관성을 제공하기 때문입니다.
Promise 생성
- Promise 생성자 인터페이스
executor에는 함수만 올 수 있으며 인자로 resolve, reject가 주입됩니다. - executor는 Promise의 실행 함수라고 불리고, Promise가 만들어질 때 자동으로 실행됩니다.
Promise가 연산을 언제 종료하는지 상관하지 않고, resolve, reject 중 하나를 무조건 호출해야 합니다.
new Promise(executor);
// 예제
new Promise((resolve, reject) => {
// 명령문
});
생성자란?
Javascript에서는 원시 타입(String, Boolean 등)을 제외한 대부분의 타입들이 객체(Object)로 구성되어 있습니다.
일반적으로 객체(Object)를 생성하는 함수를 생성자(Constructor) 함수라고 부르게 되는데, Promise 또한 객체로 구성되어 있기 때문에 생성자 함수를 이용해 Promise를 선언하게 됩니다.
function printFunc(data){
console.log(data);
}
// 생성자 함수
const obj = new Object();
const promise = new Promise(printFunc);
obj
// Print : {}
Promise의 상태
- 대기(Pending): 이행하거나 거부되지 않은 초기 상태.
- 이행(Fulfilled): 연산이 성공적으로 완료됨.
- 거부(Rejected): 연산이 실패함.
Fulfilled Promise
Promise가 만들어 질 때 executor가 실행되며, executor에서 resolve함수가 호출되기 전까지 firstPromise.then(...) 안에 있는 코드를 실행하지 않습니다.
이렇게 excutor 가 실행되어 resovle된 프로미스를 Fulfilled Promise라고도 부릅니다.
const timerPromise = new Promise((resolve, reject) => { // 이곳에 정의된 함수가 executor
setTimeout(() => {
console.log('First');
resolve();
}, 1000);
});
// 이 시점에서 timerPromise는 Fulfilled Promise라고 부를 수 있다.
timerPromise.then(() => {
console.log('Middle');
console.log('Last');
});
// Print: First
// Middle
// Last
Promise.then
Promise 안에서 resovle가 실행된 경우 then 메서드에 작성된 함수가 실행됩니다.
const resolvePromise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('First');
resolve('Resolve!'); // resolve를 실행할 때, 안에 데이터를 넣어줄 수 있습니다.
}, 1000);
});
resolvePromise.then((data) => {
console.log('Middle');
console.log('Last');
console.log(data);
})
// Print: First -> 1초 뒤에 출력됩니다.
// Middle
// Last
// Resolve!
Promise.catch
Promise 안에서 에러가 throw 되거나 reject가 실행되면 catch 메서드에 작성한 함수가 실행됩니다.
const errorPromise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('First');
reject('Error!!'); // 직접 reject를 실행하면 프로미스에서 에러가 발생한것으로 간주됩니다.
}, 1000);
});
errorPromise.then(() => {
console.log('Middle');
console.log('Last');
}).catch((error) => {
console.log('에러 발생!', error);
});
// Print: '에러 발생! Error!!'
Promise.then 더 알아보기
1. Promise에서 resolve 된 값 이용하는 방법
const firstPromise = new Promise((resolve, reject) => {
resolve('First');
});
firstPromise.then((value) => {
console.log(value);
});
// Print: 'First'
2. Promise.resolve 함수 이용
프로미스가 값을 반환하는 경우 반환되는 값은 항상 프로미스로 감싸져 있습니다.
const firstPromise = Promise.resolve('First');
firstPromise.then((value) => {
console.log(value);
});
// Print: 'First'
3. Promise.then으로 함수형 프로그래밍 체험하기
이것이 가능한 이유는 console.log 라는 함수 뒤에 괄호를 사용해서 함수를 호출하지 않고, 함수를 그대로 then에 넘겼기 때문입니다.
const firstPromise = Promise.resolve('First');
firstPromise.then(console.log);
// Print: 'First'
4. Promise.then으로 함수형 프로그래밍 체험하기 2
const countPromise = Promise.resolve(0);
function increment(value) {
return value + 1;
}
const resultPromise = countPromise.then(increment).then(increment).then(increment);
resultPromise.then(console.log);
// Print: 3
그 이외에도 유용한 프로미스 함수
'Language > Javascript' 카테고리의 다른 글
객체 리터럴 (0) | 2022.12.12 |
---|---|
비동기 함수 (Async Function) (0) | 2022.12.12 |
동기(Sync) & 비동기(Async) (0) | 2022.12.12 |
JavaScript란? (0) | 2022.12.12 |
Javascript 기본문법 (0) | 2022.11.21 |