[JS] 비동기 통신(AJAX, Promise)

JunSeok·2024년 1월 9일
0

Javascript

목록 보기
15/16

AJAX(Asynchronous Javascript And XML)

AJAX는 자바스크립트를 이용하여 웹 서버와 브라우저가 비동기적으로 통신할 수 있는 개발 기법이다.
AJAX 통신을 사용하면, 페이지의 새로고침 없이도 URL에서 데이터를 가져올 수 있어서 페이지의 일부분만 업데이트가 가능하다.

흐름

  • 클라이언트는 데이터를 가지고 있는 웹서버에 AJAX로 HTTP 요청을 할 수 있다.
  • 서버는 클라이언트가 요청한 데이터를 포함하는 응답을 보낸다.
  • 클라이언트와 서버 사이의 작업은 모두 백그라운드에서 비동기적으로 발생한다.

XMLHttpRequest

  • XMLHttpRequest는 옛날 AJAX 프로그래밍에 많이 사용되었다.
const getCountry = (country) => {
  const request = new XMLHttpRequest();
  request.open('GET', `https://restcountries.eu/rest/v2/name/${country}`)
  request.send();
  
  // 비동기 통신 load가 모두 완료되면 실행
  request.addEventListener('load', function() {
	const [data] = JSON.parse(this.responseText)  	
  })
}
getCountry('south korea')

Callback Hell

비동기 작업을 연속으로 실행하기 위해 콜백함수를 중첩하는 것을 의미한다.

  • 코드가 지저분해진다.
  • 이해하기 어렵기 때문에 유지 및 보수가 어렵다.
setTimeout(() => {
  console.log('1 second passed');
  setTimeout(() => {
    console.log('2 seconds passed');
    setTimeout(() => {
      console.log('3 second passed');
      setTimeout(() => {
        console.log('4 second passed');
      }, 1000);
    }, 1000);
  }, 1000);
}, 1000);

ES6의 promise를 통해 callback hell을 해결할 수 있다.

Promise

promise란 비동기 작업의 미래의 결과가 담길 장소이다.

  • 비동기 결과를 처리하기 위해 이벤트와 콜백함수에 의존할 필요가 없다. 그 결과값은 promise에 담길 것이기 때문.
  • 비동기 작업의 순서를 위해 chain promises를 사용함으로써 콜백지옥을 피할 수 있다.

Promise state

비동기 작업은 시간이 지나변서 값이 변하는 특징이 있다 이를 표시하기 위해 여러 state와 cycle이 존재한다.

  • pending(비동기 작업으로 결과의 값을 사용할 수 있기 전의 상태)
  • settle(비동기 작업이 끝난 상태, 이제 상태값을 바꾸는 것은 불가능하다.)
    - fulfilled(성공)
    - rejected(실패)

fetch 함수

  • fetch 함수는 promise를 리턴한다.
  • then 메서드로 promise를 처리하고 받은 값은 response 객체이다.
  • response 객체에서 실제 데이터를 읽기 위해서 json 메서드를 사용한다.
  • json 메서드는 또다시 promise를 리턴한다. 다시 then 메서드로 promise를 처리한다.
const getCountryData = (country) => {
	fetch(`https://restcountries.com/v2/name/${country}`)
  		.then(response => response.json());
  		.then(data => console.log(data));
}

Error Handling

  • catch 메서드로 에러를 캐치할 수 있고 catch 또한 promise를 리턴한다.
  • finally 메서드는 성공하든 실패하든 무조건 실행하는 메서드이다.
const getCountryData = (country) => {
	fetch(`https://restcountries.com/v2/name/${country}`)
  		.then(response => response.json())
  		.then(data => console.log(data))
		.catch(err => console.error(err))
		.finally(() => console.log('finish'));
}

Building Promise

  • 생성자를 이용하여 promise를 직접 만들 수 있다.
  • promise 생성자는 한 개의 인수를 받는다.
    - 그 인수는 promise 생성자 함수에서 바로 실행되는 실행자 함수이다.
    - 이 실행자 함수는 promise의 비동기 결과값을 생성한다.
  • 실행자 함수는 두 개의 인수 resolve 함수와 reject 함수를 받는다.
    - 이 두 함수는 promise의 결과값을 처리하는 함수이며 response 객체를 리턴한다.
    - 조건을 충족하면 resolve 함수를 호출하고 state를 fufilled로 바꾼다.
    - 조건이 불충족하면 reject함수를 호출하고 state를 rejected로 바꾼다.

예시

  • 기존의 콜백함수 기반의 비동기 작업을 promise를 이용하여 캡슐화하고 추상화를 쉽게 할 수 있다.
  • 즉 기존의 콜백함수를 promisify하여 우선순위를 더 높일 수 있다.
  • 예를 들어 콜백함수를 인수로 받는 setTimeout과 같은 비동기작업을 promise를 리턴하는 함수로 캡슐화할 수 있다.
  • 보통 fetch 처럼 promise를 리턴하는 함수 형태로 캡슐화를 한다.
  • 이를 통해 콜백지옥을 해결할 수 있고 유지보수가 더 쉬워졌다.
// 콜백지옥, 코드 이해하기 힘들다.
setTimeout(() => {
	console.log('waited more 1 second');
		setTimeout(() => {
		console.log('waited more 2 second');
			setTimeout(() => {
			console.log('waited more 3 second');
				setTimeout(() => {
				console.log('waited more 4 second');
			}, 1000);
		}, 1000);
	}, 1000);
}, 1000);

// promisify를 통해 콜백지옥 해결
const wait = (seconds) => {
	return new Promise((resolve) => {
    	setTimeout(resolve, seconds*1000);
    })
}

wait(1)
	.then(() => {
		console.log('Wait for 1 seconds');
  		// promise를 리턴해서 then 메서드로 처리
  		return wait(1);
	})
	.then(() => {
		console.log('waited for 2 seconds');
		return wait(1);
	})
	.then(() => {
		console.log('waited for 3 seconds');
		return wait(1);
	})

Async Await

  • 함수에 async 키워드를 붙이면 그 함수는 백그라운드에서 실행되는 비동기 함수가 된다.
  • async 함수는 promise를 리턴한다.
  • async 함수 안에서 한 개 또는 그 이상의 await문을 사용할 수 있다.
    - await문은 promise의 상태가 fulfill될 때까지 실행을 멈춘다.
    - 중요한 것은 async 함수는 비동기 함수이기 때문에 백그라운드에서 실행되어 메인 쓰레드의 실제 코드 실행을 멈추지 않는다는 것이다.
    • 즉 await이 자바스크립트 엔진의 call stack 실행을 막는다는 뜻이 아니다.
    • 이것이 async/await이 특별한 이유다!
      async 함수이 실행 과정을 보면, await문 때문에 동기적 코드로 보인다. 하지만 함수 자체가 백그라운드에서 실행되는 비동기함수이다.
    • 작업이 모두 끝나면 await문은 resolve된 값이 되고 그것을 변수에 저장할 수 있다.
    • 동기적 코드처럼 콜백함수 없이 promise의 결과값을 변수에 저장할 수 있기 때문에 코드가 더 쉽고 깔끔해졌다.
  • async/await은 새로운 것이 아니라 예전 then 메서드의 syntax sugar일 뿐이다. 즉 여전히 promise를 사용한다.

Error Handling

  • async/await은 try...catch를 통해 error handling을 한다.
  • catch block은 try block에서 발생한 error에 권한을 갖는다.
const test = async () => {
	try {
    	const data = await getData();
        console.log(data);
    } catch(err) {
    	console.error(err);
    }
}

Returning values from async functions

  • 일반 promise를 사용하면 then 메서드를 통해 promise를 처리한다.
  • async function은 promise를 리턴한다.
  • async function과 then 메서드를 섞어 사용하면 불편하다.
  • 이럴 경우에는 IIFE(Immediately Invoked Function Expression) 패턴을 사용해보자
  • await을 사용하려면 async 함수가 필요하니 한 번 쓰고 사라지는 IIFE 패턴을 사용하면 좋다.
(async funciton() {
 	try {
 		const data = await getData();
 	} catch(err) {
		console.error(error);
	}
 })();

Promise Combinator

all method

  • all method를 사용하면 실행순서가 상관없는 Promise를 parallel하게 처리할 수 있기 때문에 로딩 시간을 줄일 수 있다.
  • all method는 promise 배열을 받아서 처리한 결과를 배열로 받는다.
  • 하나라도 에러가 발생하면 에러가 발생한다.
const getJSON = function (url) {
	return fetch(url).then(response => {
    	return response.json();
    })
}

// 3개의 promise를 동시에 처리하고 3개의 결과를 동시에 받는다.
(async function () {
	const res = await Promise.all([
    	getJSON(`https://restcountries.com/v2/name/italy`),
		getJSON(`https://restcountries.com/v2/name/egypt`),		
		getJSON(`https://restcountries.com/v2/name/mexico`),
    ])
})()

race method

  • promise 배열을 받아서 그 중에서 가장 먼저 settle이 된 promise의 결과값을 리턴한다.
  • settle은 fulfill이라 reject와는 상관없다. 가장 먼저 settle이 된 promise만 리턴한다.
  • 예를 들어서 사용자의 인터넷 연결이 안좋을 경우, 일정 시간이 지나면 reject를 리턴하는 promise를 실행하도록 한다.
// 일정 시간이 지나면 error를 리턴
const timeout = function (sec) {
	return new Promise((resolve, reject) {
    	setTimeout(() => {
    		reject(new Error('Request took too long!'))'
    	}, sec*1000);
    })
}

// 1초 이상 지나면 timeout promise가 실행
Promise.race([
	getJSON(`https://restcountries.com/v2/name/tanzania`),
	timeout(1),
])
  .then(res => console.log(res[0]))
  .catch(err => console.error(err));

allSettled

  • promise 배열을 받아서 settle된 promise 결과를 리턴한다.
  • all method는 어느 하나라도 reject가 되면 에러가 발생했지만, allSettled method는 결과 상관없이 settle되기만 하면 다 리턴한다.
Promise.allSettled([
	Promise.resolve('success'),
	Promise.reject('fail'),
	Promise.resolve('success'),
]).then(res => console.log(res));

any

  • race method와 비슷하지만 any는 reject는 무시하고 가장 먼저 fulfill된 promise 하나를 리턴한다.
  • 만약 모든 promise가 reject되어 리턴할 fulfiil promise가 없다면 error가 발생한다.
// 가장 먼저 fulfill된 'success2'를 리턴받는다.
Promise.any([
	Promise.reject('fail'),
	Promise.resolve('success2'),
	Promise.resolve('success1')
])
  .then(res => console.log(res))	
  .catch(err => console.error(err));

참조

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/async_function
https://poiemaweb.com/js-ajax
https://poiemaweb.com/js-async
https://www.udemy.com/course/the-complete-javascript-course/

profile
최선을 다한다는 것은 할 수 있는 한 가장 핵심을 향한다는 것

0개의 댓글