IT_Study/Web

JavaScript (5) : 스레드, 비동기통신, Callback, Promise, Async/await, Ajax

__Vivacé__ 2023. 2. 11. 18:55

 

Javascript : 싱글 스레드 기반 비동기통신

 

( 자바스크립트 자체는 비동기 X, 웹 브라우저가 비동기 통신을 하게 도와주는 것)

스레드 : 하나의 프로그램 실행 작업

  • 싱글 스레드 : 한 번에 하나의 작업만 수행 가능
  • 멀티 스레드 : 한 번에 여러 개의 작업을 수행 가능

 

JavaScript에서는 멀티스레드 프로그래밍을 지원하지 않으므로, 비동기 프로그래밍을 통해 여러 작업을 병렬로 처리할 수 있는 것으로 대체하고 있음

// 비동기 프로그래밍 예시

console.log("1")
setTimeout(() => {
	console.log("2");
}, 0)
console.log("3")

// 1
// 3
// 2

 

일반 코드 실행과 비동기 코드 실행 비교를 위한 그림

 

일반적인 코드 실행 시,

  1. 기본적으로 Call Stack에 쌓임
  2. 후에 Browser Console에 가는 형식

 

비동기 코드(setTimeout 등) 실행 시,

  1. Web API로 이동
  2. 0초(위 예시에서 0ms로 설정) 간 대기를 했다가 Callback Queue로 감
  3. Event loop를 계속 감시하면서 Call Stack이 비어있는 지 확인
    • 비어 있으면 Callback Queue에서 Call Stack으로 이동

 

 

❓ setTimeout는 정확한 시간을 표시하지 않음 → 일련의 이동 과정이 복잡하기 때문

setTimeout({} , 1000) → 정확한 1000ms가 아닌, 약 1000ms 라고 생각하면 됨

 

 

위의 예시에서 알 수 있듯이, 비동기 프로그래밍은 시점 파악이 안 된다는 한계가 있음


비동기 프로그래밍 발전 과정

 

  1. Callback Function (es5)
    • 비동기 상에서 동기처럼 시점파악을 하기 위해 나온 개념
    • 비동기 작업이 완료되면 실행할 콜백 함수를 지정하여 한계를 해결할 수 있다.
    function getData(callback) {
      setTimeout(() => {    // setTimeout() : 지정된 시간(ms) 후에 특정 함수 실행하는 내장 함수
        const data = "callback data";
        callback(data);
      }, 2000);
    }
    
    function processData(data, callback) {
      setTimeout(() => {
        const processedData = data + " processed";
        callback(processedData);
      }, 2000);
    }
    
    getData((data) => {
      console.log(data);
      processData(data, (processedData) => {
        console.log(processedData);
      });
    });
    
    // 2초 뒤 callback data
    // 4초 뒤 callback data processed
    
    • 단점 : Callback Hell
      • 비동기 작업의 결과에 따라 처리할 다음 작업을 지정하는 콜백 함수의 중첩이 깊어져 코드가 복잡하고 가독성이 떨어지는 현상
    getData(function(data) {
      if (data instanceof Error) {
        // handle error
      } else {
        processData(data, function(processedData) {
          if (processedData instanceof Error) {
            // handle error
          } else {
            finalTask(processedData, function(result) {
              if (result instanceof Error) {
                // handle error
              } else {
                // use result
              }
            });
          }
        });
      }
    });
    

  1. Promise (es6)
    • Callback hell을 해결하기 위해 등장
    • 비동기 작업의 결과를 나타내는 Promise 객체를 생성하여 한계를 해결할 수 있다.
      • Promise에서 resolve를 호출 시 then으로 조회 가능 → fulfilled state
      • Promise에서 reject를 호출 시 catch로 조회 가능 → rejected state
    const obj1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello, Promise!');    // resolve() : 
      }, 1000);                        // Promise의 상태를 "완료"로 변경 후 결과값 전달 
    });
    
    obj1
      .then((message) => {             // .then() : Promise가 "완료"할 경우,
        console.log(message);          //     성공 상태에서의 결과 값을 매개변수에 전달
      })
      .catch((error) => {              // .catch() : Promise가 "실패"할 경우,
        console.error(error);          //     실패 이유를 나타내는 값이 매개변수에 전달
      });
    
    // console.log는 일반적인 메시지를 출력, 정상 작동하는 지 확인하는 데 사용
    // console.error는 오류 메시지를 출력하는 데 사용, 오류 메시지는 파란색으로 표시됨
    
    • 단점 : 결과를 받아오는 과정에서 복잡한 코드가 필요하고, 에러 처리가 복잡함
    // Callback 문제 간단하게
    
    getData()
      .then(processData)
      .then(finalTask)
      .then((result) => {
        // use result
      })
      .catch((error) => {
        // handle error
      });
    

  1. async/await (es8)
    • try-catch로 쉽게 에러 처리가 가능하고, await를 통해 비동기 처리 결과를 쉽게 받아올 수 있다.
    • 비동기 작업을 동기적으로 처리하는 것처럼 코드를 작성할 수 있도록 해주는 async/await 구문을 사용할 수 있다.
    • 사용하기 위한 조건
      • Promise로 return되는 것들에 한해서만 가능
      • 앞에 async 함수를 붙여줘야 await 사용이 가능
async function getDatas(){
  // 동기처럼 동작 --> promise처럼 깊게 쌓을 필요가 없다.

	const aData = await axios.get("A");
	// B에 요청을 하는데, A의 데이터를 보낼 수 있다.
	const bData = await axios.get("B", aData);
	// C에 요청을 하는데, B의 데이터를 보낼 수 있다.
	const cData = await axios.get("C", bData);

	// Promise 객체를 이용하는 방식이기 때문에, 위의 Promise 방식으로도 사용 가능
	
	const aData = await axios.get("A");
	aData.then(data => {}).catch(error => {});

 


Ajax (Asynchronous Javascript and XML)

브라우저와 웹 서버 간에 데이터를 교환하는 비동기 통신 기술

  • 사용자의 Event가 있으면 전체 페이지가 아닌 일부분만을 업데이트 할 수 있게 해줌
  • 자바스크립트에서는 Ajax 통신을 위해 XHR 객체를 사용

 

과거에는 무조건 데이터 갱신 시 깜빡임 (새로고침) 이 발생

 

→ 브라우저는 java 해석능력이 없음

→ 서버에서 이 java 형식을 읽어서 html 형식으로 만들어서 return

→ 이 과정 중에 새로고침이 일어난다

 

 

XHR (XMLHttpRequest)

  • Ajax방식으로 서버와 브라우저가 데이터를 주고받을때 사용하는 API
  • Promise가 아닌, XMLHttpRequest 객체를 사용하여 비동기 데이터를 가져옴
  • 콜백 함수를 사용하여 비동기 데이터 처리를 수행

fetch API

  • XHR보다 더 간단한 API
  • Promise 기반으로 동작 - async/await 구문으로 코드 가독성 높일 수 있음
  • XHR에 비해 더 많은 요청 타입(GET, POST, PUT, DELETE 등)을 지원

 

개발자도구(F12) - [네트워크] - [Fetch/XHR] 에서 비동기 방식으로 얻어온 값을 확인 가능

fetch('<https://jsonplaceholder.typicode.com/todos/1>')
      .then(response => response.json())
      .then(json => console.log(json))
async function getData(){
	try{
		// 1. fetch로 비동기 요청을 해 Promise 객체인 Response 객체를 받아옴
		const fetchData = await fetch("<https://jsonplaceholder.typicode.com/todos>");
		// 2. await에 의해 fetchData가 resolve되면, response 객체에서 json 형식으로 데이터를 가져옴
		const data = await fetchData.json();
		console.log(data);

	} catch (error) {
		console.log(error);  // Promise -> try-catch 문으로 error 출력
	}
}

getData();
// json 형식은 바로 나타낼 수 없어서
// JSON.stringfy -> JSON 형식을 문자열로 치환

async function getData(){
	try{
		const fetchData = await fetch("<https://jsonplaceholder.typicode.com/todos>");
		const data = await fetchData.json();
		console.log(data);

		document.querySelector('div').insertAdjacentHTML('beforeend', JSON.stringfy(data));
	} catch (error) {
		console.log(error);
		document.querySelector('div').textContent = "데이터를 가져올 수 없습니다.";
	}
}

getData();    // error 시 "데이터를 가져올 수 없습니다" 가 웹에 표시

axios

브라우저와 Node.js 환경에서 사용하는 HTTP 통신 라이브러리

다음과 같은 면에서 fetch API보다 더 나은 기능을 제공하고 있음

 

 

  1. 비동기 처리
    • fetch API : 여러 개의 요청을 관리하고, 에러 처리, 결과 값을 쉽게 얻을 수 있는 방법이 없음.
    • axios : Promise 기반의 API를 제공하여, 비동기 처리와 관련된 기능을 간편하게 구현 가능

 

  1. 글로벌 설정
    • fetch API : 각각의 fetch 요청마다 글로벌 설정을 다시 적용해야 함
    • axios : 글로벌 설정을 제공 / 요청 URL의 기본 접두사, 헤더 등을 설정 가능

 

  1. 인터셉터 - 요청 & 응답 데이터 가공 등을 한 곳에서 관리하는 기능
    • fecth API : 인터셉터 설정이 없어, 직접 구현해야 함
    • axios : 인터셉터를 통해 요청이나 응답에 대한 처리를 간편하게 수행 가능

 

  1. 캐시 관리:
    • fecth API : axios보다 제한된 캐시 관리 기능 제공 / HTTP 요청을 보낼 때 HTTP 캐시 헤더를 설정하여 캐시 관리를 구현 가능
    • axios : fetch API보다 더 확장된 캐시 관리 기능을 제공, 요청을 캐시하거나, 캐시에서 응답을 가져올 수 있음

 

// 예제
axios.get("API 주소") // 요청 성공 시 resolve를 실행
											// 요청 거부 시 rejected를 실행

axios.get("주소")
.then(data => axios.get("B", data))
.then(d => axios.get("C", data))
// 외부 라이브러리이기 때문에, 가져와 주어야 함

	// HTTP Method

	// GET POST PATCH PUT DELETE  (CRUD)
  // GET -> 데이터 가져오기
  // POST -> 데이터 작성
  // PATCH, PUT -> 데이터 수정
  // DELETE -> 데이터 삭제

	// 1. Promise then 이용
	axios.get('<<a href=https://jsonplaceholder.typicode.com/todos>https://jsonplaceholder.typicode.com/todos</a>>').then(res => {
		console.log(res);
		console.log(res.data);
	}).catch(error => {console.log(error)});

	// 2. async await
	async function getData(){
		const result = await axios.get('<<a href=https://jsonplaceholder.typicode.com/todos>https://jsonplaceholder.typicode.com/todos</a>>');
		console.log(result.data);
	}

	getData();