이전 글
이전 글에서는 비동기 처리를 통해 데이터를 가져올 수 있었고
데이터를 가져오는 과정에서 콜백 헬과 에러 처리에 대한 곤란함을 겪었다.
이를 개선하기 위해 ES6에서 도입된 프로미스 객체에 대해 알아보자.
프로미스 객체
프로미스란 비동기 작업이 맞이할 미래의 완료 or 실패와 그 결과 값을 말한다.
....? 무슨 말인지 정말 모르겠지만, mdn에서 본 내용이다.
아무튼 비동기 작업을 돕기 위해 ES6에서 도입된 객체이다.
프로미스는 new 연산자를 통해 생성자 함수로 호출하며, 인자로 콜백을 전달해줘야 한다.
이 콜백함수는 인자로 resolve, reject라고 부르는 2가지 함수를 받는다.
const hi = new Promise((resolve, reject) => {
console.log("hi");
});
console.log(hi);
new Promise의 인수 (resolve,reject)=>{console.log}가 콜백함수이다.
Promise 객체를 담은 hi변수를 출력해보면,
콜백 내에서의 console.log가 실행되었다.
hi 변수는 Promise{<pending>}
이라는 알 수 없는 결과가 나왔다.
앞서 Promise는 객체는 미래의 완료/실패와 결과를 담고있다고 했다.
- Prototype : Promise 객체
- PromiseState : 미래의 완료/실패
- PromiseResult : 결과
각각 이렇게 의미한다.
Promise는 3가지 상태를 가진다.
pending : 비동기 처리가 아직 완료되지 않음.
fullfilled : 비동기 처리 완료(성공)
rejected : 비동기 처리 완료(실패)
생성 직후 프로미스는 pending 상태를 가진다.
콜백함수의 인자로 resolve, reject 함수를 받는다고 했다.
이 두가지 함수를 사용해 상태를 변경할 수 있다.
resolve : 프로미스를 fullfilled 상태로 변경
reject : 프로미스를 rejected 상태로 변경
이제 콜백함수 내에서 resolve, reject를 호출하여 Promise 상태를 변경할 수 있다.
let dataLoaing = true;
const hi = new Promise((resolve, reject) => {
if (dataLoaing) {
// 비동기 작업 성공
resolve("Data");
} else {
// 비동기 작업 실패
reject("Not");
}
});
console.log(hi);
임의로 설정한 dataLoading이라는 변수로 비동기 작업의 성공과 실패 상태를 간단하게 나눠봤다.
비동기 작업이 성공했다면?
resolve 함수만을 호출하거나, PromiseResult로 전달할 데이터를 인수로 넣어준다.
비동기 작업이 실패했다면?
reject 함수만을 호출하거나, PromiseResult로 전달할 데이터를 인수로 넣어준다.
현재 코드는 resolve("Data")를 실행하였고, "fullfilled" 상태 + "Data" 결과를 보여준다.
Promise.prototype.then
then 메서드를 이용하면 resolve를 통해 보낸 데이터를 받을 수 있다.
그래서 resolve/reject가 실행된 그 시점에 데이터를 이용해 수행할 다음 작업을 미리 정의할 수 있다.
then 메서드 역시 2가지 콜백 함수를 인자로 받는다.
첫번째 콜백 함수는 resolve에 대한 처리를 담당한다.
두번째 콜백함수는 reject에 대한 처리를 담당한다.
let dataLoaing = true;
const hi = new Promise((resolve, reject) => {
if (dataLoaing) {
// 비동기 작업 성공
resolve("Resolve");
} else {
reject("Reject");
}
});
hi.then(
(res) => console.log(`Resolve : ${res}`),
(rej) => console.log(`Reject : ${rej}`)
);
// 사실 res, rej에 대한 두가지 콜백을 넣는 방법보다
// resolve에 대한 콜백만을 넣어두고,
// reject의 처리는 별도의 catch에서 수행하는 편이다.
then 메서드의 콜백함수에서 resolve의 결과로 전달받은 String : "Resolve"를 출력한다.
(사실 then의 2번째 인자는 잘 사용되지 않는다. 아래의 catch가 있기 때문.)
Promise.prototype.catch
사실, reject 상태에 대한 후속처리는 catch를 통해 이루어지는 것이 정석이다.
try catch문의 catch와 동일한 작업을 수행한다고 생각하면 된다.
let dataLoaing = false;
const hi = new Promise((resolve, reject) => {
if (dataLoaing) {
// 비동기 작업 성공
resolve("Resolve");
} else {
reject("Error : Reject");
}
});
hi.then((res) => console.log(`Resolve : ${res}`))
.catch((err) => console.log(err));
프로미스 객체에서 reject가 실행되었고 catch에서 에러를 잡아 출력했다.
예시이므로 데이터로 "Error:Reject" 텍스트를 전달했다.
실제 코딩시에는 Error 객체 혹은 에러에 대한 정확한 내용을 담아주는 편이 좋다.
조금 중요한 사실이 있다.
then, catch 모두 기본적으로 프로미스를 반환한다.
then의 콜백 함수에서 프로미스가 아닌 값을 반환해도 암묵적으로 프로미스로 생성해 반환한다.
또한 try-catch-finally 구문처럼,,
then-catch-finally로 공통으로 수행할 코드를 finally에 정의할 수도 있다.
Good! 앞서 배운 비동기 처리에서의 문제 중 하나였던 에러 처리도 이제 가능해졌다.
Promise Chaining
잠깐 언급한 프로미스 체이닝을 배워보자.
이것은 이전 포스팅에서 다룬 콜백 헬에 대한 해결이 될 수 있다.
then, catch, finally은 프로미스를 반환한다고 했다.
프로미스를 반환하는 것이 무슨 의미냐면,
[1,2,3].map(value=>value+1).map(value=>value+1) 와 같이 반환값을 이용해 체이닝을 할 수 있다는 것이다.
이전 포스팅에서 userId로부터 userInfo를 가져오는 작업을 프로미스 객체를 가지고 다시 수행해보자.
이전의 코드
const get = (url, cb) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
cb(JSON.parse(xhr.response));
} else {
// 에러 처리
}
};
};
const url = "https://jsonplaceholder.typicode.com";
try {
get(`${url}/안녕하세요/1`, (res1) => {
console.log(res1);
get(`${url}/users/${res1.userId}`, (res2) => {
console.log(res2);
});
});
} catch (err) {
console.log(`Error : ${err}`);
}
// 이전의 코드였다. get에 전달한 콜백 함수 내부에서 또 get을 호출하는 것을 볼 수 있다.
Promise 코드
const get = (url) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.response));
} else {
reject("Error!");
}
};
});
};
const url = "https://jsonplaceholder.typicode.com";
get(url + "/posts/1").then((res) => {
console.log(res);
return get(url + "/users/" + res.userId);
}).then((res2) => console.log(res2));
// Promise 객체를 이용해 수정한 코드의 모습이다.
위와 아래의 코드는 동일한 작업이다.
위에 비해서 아래의 코드가 조금은 더 가독성이 좋은 것 같다.
위의 코드는 try catch문이 있으나, 처음 get의 오류만을 감지할 뿐 콜백내부에서 발생한 오류는 처리를 하지 못한다.
아래의 코드에선, get함수에서 리턴된 Promise 객체를 then을 통해 받는다.
then에서 res 인자를 통해 전달받은 데이터 중 res.useId를 이용하며
get함수를 다시 실행하여 then의 결과로 내보낸다.
그렇게 내보낸 두번째 Promise를 then을 통해 받아서 데이터를 출력하고 끝낸다.
Promise 객체와 프로토타입 메서드 then,catch,finally 덕에 기존 콜백 헬과 에러 처리 문제를 해결할 수 있었다.
다음으로는 비동기 처리 예시를 몇가지 배워보자.
참고한 자료
이어지는 글
'JavaScript > theory' 카테고리의 다른 글
[자바스크립트] 마이크로 태스크 큐와 프로미스 (0) | 2021.08.22 |
---|---|
[자바스크립트] 프로미스를 이용한 비동기 작업 병렬 처리 (0) | 2021.08.22 |
[자바스크립트] 비동기로 데이터 가져오기, 콜백 헬 (0) | 2021.08.22 |
[자바스크립트] 비동기 작업, 이벤트 루프와 태스크 큐 (1) | 2021.08.22 |
[자바스크립트] 동기와 비동기 이해하기. (1) | 2021.08.21 |