이전 글
이전 글에서는 어떻게 자바스크립트에서 비동기 작업이 수행되는지를 열심히 알아봤다.
이제 비동기 작업과 주된 목적중 하나인 데이터를 가져오는 작업에 대해 알아보자.
비동기로 데이터 요청하기. ( AJAX, XHR 객체 )
AJAX(Asynchronous JavaScript And XHR)
자바스크립트와 XHR를 이용해 비동기로 뭔가를 하겠다는 거다.
(= 그래서 javascript, xhr를 이용한 서버와의 비동기 통신 방법이라 불린다.)
특히 브라우저가 서버에게 AJAX로 데이터를 요청하고,
서버의 응답 데이터를 받아 웹페이지를 동적으로 갱신하는 데 자주 쓰인다.
AJAX에서는 XHR(XMLHttpRequest) 객체를 기반으로 한다는데,
우리도 XHR 객체를 이용해 URL에 요청하고 데이터를 받아보자.
const get = (url) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
// 응답 출력
console.log(xhr.response);
} else {
// 에러 처리
}
};
};
get("https://jsonplaceholder.typicode.com/posts/1");
console.log("Data Load Completed");
이 코드는 XMLHttpRequest 객체를 이용해, url에 GET 요청을 보내고 응답을 받는 get함수다.
url에 요청을 보내면, xhr.onload 메서드에서 응답을 처리하도록 설계되어 있다.
xhr.onload에는 응답 상태코드=200일 때, 받은 데이터를 출력하도록 했다.
get호출 뒤의 Data Load Completed가 먼저 출력된다.
그래서 get 함수의 onload 메서드가 비동기로 동작함을 우선 예상할 수 있다.
여기서 onload는 이벤트 핸들러로 등록이 되고, 즉시 종료된다.
그래서 바로 get함수 호출 다음 코드인 Completed를 출력하고, 응답을 받은 뒤 onload가 나중에 출력된다.
만약 실제 요구사항이 Data Load Completed가 가장 나중에 출력되야 한다면?
이 코드는 개발자가 의도한대로 동작하지 않고 있다.
순차적 처리를 위한 코드 수정
어떻게 해야 할 지 방법을 생각해보자. 두가지 대안이 있겠다.
- onload 메서드내에서 console.log("Data Load Completed")를 출력하도록 포함한다.
- onload 메서드가 모두 끝나야만 console.log("Data Load Completed") 메시지가 출력되도록 설계한다.
2번이 좋아보이긴 하지만, 우리가 배운 내용으로는 2번 작업을 수행할 수 없다.
1번 구현사항으로 바꿔보자.
const get = (url) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
// 응답 출력
console.log(xhr.response);
console.log("Data Load Completed"); // onload 메서드 안으로 옮긴다.
} else {
// 에러 처리
}
};
};
get("https://jsonplaceholder.typicode.com/posts/1");
console.log("End");
onload 메서드 내에 출력을 정의해서, 이제 데이터 출력 뒤에 완료 메시지를 띄워준다.
그러나 추가 요구사항이 생겼다.
"End"로 끝! 임을 출력해달라고 한다.
onload는 콜스택이 비어야 호출된다.
여전히 get함수 아래에 정의한 "End"가 가장 먼저 출력될 것이다.
결국 onload 메서드 내부에 console.log("End")를 추가해야 한다.
이처럼 A의 뒤에 B를 수행하는 방법으로, "콜백패턴"에 대해 먼저 알아보자.
비동기로 가져온 데이터 기반으로 새로운 작업 수행하기
A 비동기 요청으로 A'데이터를 받아와서,
A'데이터를 기반으로 B요청을 수행해달라고 한다.
아래 코드는 A비동기 요청으로 가져온 데이터 중 userId(A')를 이용해서
userInfo(B')를 불러오는 코드다.
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";
get(`${url}/posts/1`, (res1) => {
console.log(res1);
console.log("chapter1. Success");
get(`${url}/users/${res1.userId}`, (res2) => {
console.log(res2);
console.log("chapter2. Success");
});
});
get함수는 url, cb(callback)을 인자로 받는다.
url에 요청을 하고, 응답을 res1 변수에 담는다.
res1을 출력하고, res1.userId를 이용해 userInfo를 가져와 출력하는 코드다.
함수 내에서 함수를 실행한 결과, 의도한 결과를 얻을 수 있었다.
콜백 헬 그리고 에러 처리
하지만 문제는 단순히 가져온 데이터를 1회 이용했을 뿐인데도..
가독성이 좋지 않다.
get에 콜백으로 전달한 함수에서, get함수가 다시 호출되면서 또 다른 콜백을 전달하고 있다.
이처럼 비동기 함수의 처리 결과를 가지고 또 다시 비동기 함수를 호출하게 되면서
콜백 함수 호출이 중첩되는 현상을 콜백 헬이라고 한다.
작업이 여러 번 이루어진다면???
get->get->get->get->get이 중첩되는 아주 환상적인 코드를 얻게 된다.
또한
try {
get(`${url}/안녕하세요/1`, (res1) => {
console.log(res1);
get(`${url}/users/${res1.userId}`, (res2) => {
console.log(res2);
});
});
} catch (err) {
console.log(`Error : ${err}`);
}
이처럼 콜백이 중첩되는 중간에 오류라도 발생하면?
그 다음 코드는 실행될 수 없다.
오류를 잡는 방법. try/catch!
동기적으로 수행되는 코드에서는 try / catch 문을 이용해 오류를 잡을 수 있다.
get 함수는 실행 즉시 종료되면서 콜스택에 존재하지 않으므로, try catch문도 끝나버린다.
그러므로 onload에 정의된 콜백이 호출되기 전에 종료되는 try catch 문에서는, 에러를 잡을 수 없다.
그래서 에러가 발생하여 프로그램이 멈출 수 있다.
다음 게시글에서 콜백 헬과 에러 처리를 개선을 위해,
ES6에서 소개된 Promise 객체에 대해 알아보자.
참고한 자료
이어지는 글
'JavaScript > theory' 카테고리의 다른 글
[자바스크립트] 프로미스를 이용한 비동기 작업 병렬 처리 (0) | 2021.08.22 |
---|---|
[자바스크립트] 프로미스 객체 (0) | 2021.08.22 |
[자바스크립트] 비동기 작업, 이벤트 루프와 태스크 큐 (1) | 2021.08.22 |
[자바스크립트] 동기와 비동기 이해하기. (1) | 2021.08.21 |
[자바스크립트] enum을 배우고 구현해보자. (0) | 2021.08.02 |