2023.03.01
앞서 공부했던 비동기 처리를 할 때 많이 사용되는 자바스크립트 객체라고 합니다.
주로 서버에서 받아온 데이터를 화면에 표시할 때 사용합니다. 앞서 보았던 $.get()
과 같은 함수로 서버에 데이터를 요청하였는데, 아직 데이터를 받지 않은 상태에서 화면에 데이터를 표시하려고 하면 오류가 발생하거나 빈 화면이 나타나게 됩니다. 비동기 처리로 인해 데이터를 받기 전에 다음 구문을 실행하기 때문입니다. 따라서 이런 문제점을 해결하기 위한 방법 중 하나가 Promise
입니다.
Promise
를 사용하면 다음과 같은 장점이 있습니다.
ajax로 작성한 간단한 통신 코드를 보면
const getData = (callbackFunc) => {
$.get('url', (response) => {
callbackFunc(response);
});
};
getData((dat) => {
console.log(dat);
});
이 경우 비동기 처리를 위해 콜백 함수를 사용하였습니다. 위 코드를 Promise
로 나타내면 다음과 같습니다.
const getData = (callback) => {
return new Promise((resolve, reject) => {
$.get('url', (response) => {
resolve(response);
});
});
};
getData().then((dat) => {
console.log(dat);
});
콜백 함수와 달리, resolve()
, reject()
, then()
과 같은 함수를 사용하는 구조로 바뀌었습니다. 이를 이해하기 위해서는 Promise의 3가지 상태에 대해 알아야 했습니다. 3가지 상태에 따라 비동기 작업을 실행, 그 다음 후속 처리 메소드인 then()
, catch()
를 통해 비동기 처리 결과 메시지를 전달받아 처리합니다.
여기서 말하는 상태는 Promise의 처리 과정을 의미합니다. 상태에는 3가지가 존재하는데
Promise로 등록한 비동기 작업이 완료되기까지 대기 중인 상태입니다. new Promise()
메서드를 호출하면 Pending 상태가 됩니다. 그러니까 생성된 직후부터 resolve()
혹은 reject()
가 호출되기 전까지의 순간을 Pending 상태라고 할 수 있습니다.
new Promise();
위 메소드를 호출할 때 콜백 함수를 선언할 수 있고, 파라미터로는 resolve
, reject
가 있습니다.
new Promise((resolve, reject) => {
...
});
콜백 함수의 파라미터인 resolve()
를 실행하면 Fullfilled(이행) 상태가 됩니다. 이러한 상태가 되면, then()
을 이용하여 비동기 처리 결과를 전달받을 수 있습니다.
new Promise((resolve, reject) => {
resolve();
});
위와 같이 resolve()
가 실행되면 .then()
으로 등록된 첫번째 함수를 실행합니다. 비슷하게 reject()
가 실행되면 .then()
으로 등록된 두번째 함수를 실행합니다. .catch()
로 등록된 함수는 비동기 처리 혹은 .then()
메소드 실행 중 발생한 에러(예외)가 발생하면 호출됩니다.
const getData = () => {
return new Promise((resolve, reject) => {
var value = 100;
resolve(value);
});
};
getData().then((resolvedData) => {
console.log(resolvedData);
});
위 코드의 경우 value = 100
이라는 구문이 오류없이 실행되고 그 값이 resolve()
함수를 통하여 .then()
에 등록된 첫번째 함수로 전달됩니다. 그 결과 100
이라는 값이 출력됩니다.
reject()
가 호출되면, Rejected(실패) 상태가 됩니다. 이 상태가 되면 .then()
으로 등록된 두번째 함수 혹은 .catch()
로 등록된 함수를 통해 실패 처리의 결과를 받을 수 있습니다.
두가지의 경로로 에러를 받을 수 있지만, 미묘한 차이가 있습니다. .then()
의 두번째 함수는 reject()
호출 시에만 작동하는 반면, .catch()
로 등록된 함수는 .then()
에서 호출한 함수에서 오류가 났을 경우에도 호출됩니다. 따라서 비동기 처리의 전체적인 오류를 다루기 위해서는 .catch()
함수가 더 적절합니다.
const getData = () => {
return new Promise((resolve, reject) => {
$.get('/asdf', (response) => {
if (response) resolve(response);
reject(new Error("failed"));
});
});
};
getData().then((dat) => {
console.log(dat);
}).catch((err) => {
console.error(err);
});
위 코드는 '/asdf' 로 요청하여 응답을 받으면, 그 결과를 콘솔 창에 출력하고 에러가 뜨면 콘솔 창에 에러를 보여줍니다.
Promise는 여러개를 연결할 수 있습니다. .then()
메소드를 호출하면 새로운 Promise
객체가 반환됩니다. 따라서 체이닝이 가능하고, 이와 연결된 작업을 콜백 지옥을 피하면서 작성할 수 있게 됩니다. 예를 들어
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 3000);
})
.then((data) => {
console.log(data);
return data + 10;
})
.then((data) => {
console.log(data);
reutrn data + 10;
})
.then((data) => {
console.log(data);
});
위 코드를 실행하게 되면
1
11
21
이 출력됩니다. 이 외에도 실제 웹 서비스를 생각해보면, 유저 정보를 받아 이를 검사하고 화면에 표시하는 로그인 로직의 경우를 생각해 보았습니다. 이 경우에는
getData(info)
.then(parse)
.then(auth)
.then(display)
와 같은 구조로 콜백 지옥을 피하면서 가독성과 유지보수에 유리한 로직을 짤 수 있습니다. 이렇게 체이닝을 통한 처리 시에, 에러 핸들링의 경우 .then()
이 연결되는 도중 에러가 발생하면 .catch()
로 이동하며 그 다음 이어지는 .then()
은 실행되지 않습니다.
그 외에 Promise 에 있는 메소드들 입니다. all()
, race()
, allSettled()
가 있습니다.
여러 Promise들을 한번에 병렬 처리를 할 수 있습니다. 가장 마지막으로 끝나는 Promise를 기준으로 수행되고, 모든 Promise들이 fullfilled 상태가 되면 결과값을 배열에 담아 새로운 Promise를 반환합니다. 만약 Promise들 중 하나라도 에러(rejected)가 발생하면 rejected 상태가 되고 수행을 종료합니다.
all()
메소드와는 다르게 병렬로 처리하지 않고 가장 먼저 끝나는 Promise의 결과값을 resolve()
로 반환합니다.
all()
메소드와 비슷하지만, 이 메소드는 에러가 발생해도 나머지 모든 Promise를 실행하여 결과적으로 수행된 상태와 결과값을 담은 배열을 담아 resolve()
로 반환합니다.
또다른 차이점은 반환된 배열에는
[
{ status: 'fullfilled', value: 1 },
{ status: 'rejected', reason: 2 }
]
와 같은 결과 배열을 반환하는데, fullfilled의 경우 value, rejected의 경우 reason을 프로퍼티로 갖습니다.