AJAX 와 Promise( feat. 콜백 패턴, Promise, Async/Await

taehyung·2024년 5월 21일
0

JavaScript

목록 보기
12/15
post-thumbnail

AJAX 통신이 모던 웹에 어떠한 영향을 주었는지는 전 포스팅에서 이야기하였듯, 웹페이지의 부분적 렌더링을 통한 이점이 많았습니다.

그렇다면 단점은 어떻고, 또 그 단점을 해결하는 방법은 무엇이 있을까요 ? 한 번 알아보겠습니다.

비동기 HTTP 통신의 단점

let post;

const get = url => {
	const xhr = new XMLHttpRequest();
	xhr.open('GET',url);
	xhr.send();
  	xhr.onload = () => { //비동기로 동작
    	if(xhr.status === 200){
        	post = JSON.parse(xhr.response);
        }
    }
}

get('https://jsonplaceholder.typicode.com/posts/1');

console.log(post);

jsonplaceholder API 서버의 게시글을 가져오는 요청을 했다. 과연 post 변수에는 값이 제대로 할당이 되었을까 ?

이상하다. 서버에 제대로 요청해서 그 결과를 상위 실행 컨텍스트에 변수에 할당했는데 값이 제대로 전달되지 않았다.

XMLHttpResquest 함수의 onlaod 메서드는 비동기로 작동한다. get 함수는 비동기로 작동하므로 get 함수의 실행 컨텍스트가 열리고 비동기로 작동하는 코드들을 호출 스케줄링하고 그 즉시 실행 컨텍스트는 종료된다.


get 함수는 마이크로 태스크큐에서 대기하고 있다가 모든 동기 코드의 콜스택이 비어졌을 때 비로소 실행된다. 비동기 코드는 언제나 동기 코드가 모두 실행된 후에 콜스택에 쌓이므로 그 결과값을 상위 스코프에 변수에 할당하거나, 외부로 반환할 수 없는 문제가 있다.

따라서, 비동기 함수의 처리 결과에 대한 후속처리는 비동기 함수 내부에서 해야만 한다. 이때 처리결과에 대한 후속처리를 수행하는 콜백 함수를 전달하는것이 일반적이다.


해결 방법

1. 콜백 함수

콜백 함수란 함수의 매개변수로 전달되는 함수를 말한다.

const url = 'http://jsonplaceholder.typicode.com';

try{ // 에러가 잡힐까 ?
  const get = (url, callback) => {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', url);
      xhr.send();
      xhr.onload = () => { // 비동기로 동작
          if (xhr.status === 200) {
              callback(JSON.parse(xhr.response));
          }
      };
  };
}catch(e){
	console.err(e)
}

get(`${url}/posts/1`, ({ userId }) => {
    console.log(userId); //userId 획득
    get(`${url}/users/${userId}`, (user) => {
        console.log(user); //userId 를 이용하여 user 정보 획득
    });
});

단점

  1. 콜백 헬
    두 번의 API 요청만에 들여 쓰기가 깊어지고 가독성이 떨어지는데 이를 콜백 헬이라고 말하며 콜백 패턴의 단점이다.
  2. 에러 핸들링
    비동기 코드를 포함한 비동기 함수는 실행 컨텍스트가 호출 스케줄링 후 그 즉시 종료하기 때문에 에러 핸들링 또한 콜백 함수에서 해야 하는데, 이는 가독성을 더욱 악화 시킨다.

2. Promise

Promise 는 ES6 사양에 도입된 표준 빌트인 객체로 비동기 처리를 수행할 콜백 함수를 인수로 전달받는다.
콜백 함수는 resolve, reject를 인수로 전달받고, 비동기 통신의 성공 처리를 resolve, 실패 처리를 reject 함수가 담당한다.

const post = new Promise((resolve,reject)=>{
	//Promise 의 콜백함수 내부에서 비동기를 처리한다.
      const xhr = new XMLHttpRequest();
      xhr.open('GET', 'http://jsonplaceholder.typicode.com/posts/1');
      xhr.send();
      xhr.onload = () => { // 비동기로 동작
          if (xhr.status === 200) {
        	resolve(JSON.parse(xhr.response))
          }else{
            reject('통신에 실패하였습니다');
          }
      };
})

post.then((res)=>{console.log(res)});

resolve(성공)과 reject(실패)

  1. resolve (성공)
    Promise 콜백 함수 내부에서 통신에 성공했을 경우 호출하는 함수로 매개변수에 성공한 값을 전달할 수 있다.
    resolve(결과값) 을 호출하면 .then() 메서드를 통해 성공한 값을 받아 후속처리가 가능하고, Promise의 상태를 fulfilled 상태로 변경한다.

  2. reject (실패)
    Promise 콜백 함수 내부에서 통신에 실패했을 경우 호출하는 함수로 매개변수에 에러 객체나 메시지를 주로 전달한다.
    reject(에러) 를 호출하면 .catch() 메서드를 통해 에러를 캐치하여 후속 처리가 가능하고, Promise의 상태를 rejected 상태로 변경한다.

Promise 의 상태

Promise의 상태는 pending, fulfilled, rejected 세 가지 상태가 있다.

  • pending ( 대기 )
  • fulfilled ( 성공 ) : resolve 함수가 호출되면 이 상태가 됨
  • rejected ( 실패 ) : reject 함수가 호출되면 이 상태가 됨
  • settled ( 결과에 상관없이 Promise 가 완료된 상태 )

콜백 헬 해소하기

then 문을 체이닝하여 콜백 패턴의 콜백 헬을 해소할 수 있는 방법으로 비동기 코드의 실행 순서를 보장하여 동기 코드처럼 사용할 수 있다.

then 에 전달되는 콜백함수에 return 문을 사용하면 다음 then 의 콜백 함수의 매개 변수로 받아서 사용할 수 있습니다.

콜백헬이 발생하는 콜백 패턴을 Promise 를 사용한 패턴으로 변경해보았다.

const url = 'http://jsonplaceholder.typicode.com';

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(new Error('통신에 실패하였습니다'))
            }
        };	
    })
}

get(`${url}/posts/1`).then(({userId})=>{
	console.log(userId);
	return get(`${url}/users/${userId}`)
}).then((user)=>{
  	console.log(user)
})

Promise의 정적 메소드

  • Promise.all() : 여러개의 Promise 를 병렬 처리한다. 모든 Promise 가 성공해야만 모든 프로미스의 데이터를 배열로 받을 수 있다. 하나라도 실패하면 즉시 실행을 종료한다.
  • Promise.race() : 여러개의 Promise 를 병렬 처리한다. 가장 빠르게 완료된 Promise 를 반환한다. 하나라도 실패하면 즉시 실행을 종료한다.
  • Promise.allSetteld() : 여러개의 Promise 를 병렬 처리한다. 모든 Promise 의 처리 결과를 가진 배열을 반환한다.

fetch API

우리는 지금까지 Web API 의 XMLHttpRequest 객체를 이용하여 서버와 통신했다. 이 API 는 개발자가 직접 초기화부터 시작해서 파싱까지하는 번거로움이 있다. 최근에 추가된 fetch API 는 더 간단하게 HTTP 요청을 보내고 프로미스를 지원하여 콜백 패턴에서 자유롭다.

fetch API 는 HTTP 응답을 나타내는 Response 객체를 래핑한 Promise 객체를 반환한다. 즉, 서버의 응답을 Promise 로 반환한다.

fetch API 를 사용하면 기존 XMLHttpRequest API 보다 훨씬 간단하게 작성이 가능하다.


const url = 'http://jsonplaceholder.typicode.com';

const get = (url)=>{
  return fetch(url,{method:'GET'}).then(res=> res.json());
}

get(`${url}/posts/1`).then(({userId})=>{
	console.log(userId);
	return get(`${url}/users/${userId}`)
}).then((user)=>{
  	console.log(user)
})

async/await

ES8 사양에 도입된 프로미스를 기반으로 비동기를 처리하는 문법이다.

async/await 을 사용하면 Promise 의 then,catch,finally 후속처리 메소드에 콜백을 전달하는 패턴을 사용하지 않고 마치 동기 처리를 하듯이 Promise 를 사용할 수 있다.

const url = 'http://jsonplaceholder.typicode.com';

const get = async (url) => {
  try{// 에러처리가 편하다.
  	const response = await fetch(url,{method:'GET'});
  	const data = await response.json();
  }catch(e){
  	console.err(e);
  }
}
  1. async 함수는 언제나 프로미스를 반환한다. 명시적으로 Promise 를 반환하지 않더라도 암묵적으로 resolve 된 Promise 를 반환한다.

  2. await 키워드는 반드시 async 함수 내부에서 사용해야한다.

  3. await 키워드는 반드시 Promise 앞에서 사용해야한다.
    await 키워드는 Promise 가 settled 상태가 될 때까지 대기하고 Promise가 resolve 한 처리 결과를 반환한다.

  4. 또한 에러처리가 매우 간단하고 try 블록 내부에서 발생하는 에러도 다 잡아낼 수 있다. async 함수 내에서 catch 문을 사용해서 에러 처리를 하지 않으면 async 함수는 발생한 에러를 reject 하는 프로미스를 반환한다.

profile
Front End

0개의 댓글