JS - 비동기 프로그래밍

문한성·2023년 4월 5일
0

공부

목록 보기
14/28
post-thumbnail

1. 자바스크립트의 비동기식 처리

• 자바스크립트는 싱글 스레드

자바스크립트는 싱글스레드(하나의 메인스레드) 런타임을 가진 동기식 언어이다.
분기문, 반복문, 함수 호출 등이 동기적으로 실행되며, 이 때 코드의 처리는 코드의 흐름과 동일하다. 싱글 스레드 환경에서 메인 스레드를 긴 시간 점유하면, 프로그램이 멈출 수도 있다.

하지만, 자바스크립트는 브라우저에서 별도의 API를 사용하여 비동기적으로 작업을 처리할 수 있다.

자바스크립트에서 비동기 코드를 처리하는 모듈은 이벤트 루프(event loop), 태스크 큐(task queue), 잡 큐(job queue) 등으로 구성된다.
API 모듈은 비동기 요청을 처리 후 태스크 큐에 콜백 함수를 넣는다. 자바스크립트 엔진은 콜 스택이 비워지면, 태스크 큐의 콜백 함수를 실행한다.

• 자바스크립트의 비동기 내장함수

자바스크립트는 비동기 내장함수를 제공하는 데, 그것은 바로 setTimeout, XMLHttpRequest, fetch() 이다.

XMLHttpRequest는 현재는 잘 사용하지 않는 것으로 현재는 fetch를 많이 사용한다. fetch는 Promise 기반이기 때문에 Promise 이후에 살펴보겠다. 따라서 여기선 setTimeout에 대해서만 간단하게 보도록 하자.

  • setTimeout / clearTimeout
    • setTimeout() 함수는 특정 코드를 바로 실행하지 않고 일정 시간동안 지연시킨 후 실행한다.
    setTimeout(function() { 코드 or 콜백함수 }, 지연시간);
setTimeout(() => console.log("2초 후에 실행됨"), 2000);
// 지연 시간은 밀리초 단위로 기입. 1초 -> 1000
// 2초 후에 콘솔에 "2초 후에 실행됨" 출력

• clearTimeout() 함수는 setTimeout()을 취소, 중지시킨다.
clearTimeout( [식별자] );

아래는 clearTimeout을 이용한 디바운싱 예제이다.

  • 디바운싱(debouncing) : 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것
var timer;
document.querySelector('#input').addEventListener('input', function(e) {
  if (timer) {
    // input 될 때 마다 이벤트가 발생하지 않도록 n-1 번째 이벤트 취소
    clearTimeout(timer);
  }
  // n번째 이벤트만 남겨서 마지막 이벤트만 실행되게
  timer = setTimeout(function() {
    console.log('여기에 ajax 요청', e.target.value);
  }, 200);
});

• 자바스크립트의 비동기 처리 3가지

① Callback Function
② Promise
③ async/await

2. Callback Hell

콜백 함수는 파라미터로 함수를 전달 받아 함수의 내부에서 실행하는 함수이다.

그런데 콜백 함수는 자칫하면 '콜백 지옥 (callback hell)'이 발생할 수 있다.
콜백 지옥이란, 함수의 매개변수로 넘겨지는 콜백 함수가 반복되어 코드의 들여쓰기 수준이 감당하기 힘들어질 정도로 깊어지는 현상이다.

이러한 콜백 지옥을 해결하기 위해 새로운 비동기 처리 방법으로 Promise가 탄생한 것이다. Promise에 대해 알아보도록 하자.

3. Promise

Promise는 자바스크립트에서 제공하는 비동기를 간편하게 처리할 수 있도록 도와주는 객체이다.

• Promise의 상태와 resolve(), reject()

Promise의 상태는 프로세스가 기능을 수행하고 있는 중인지, 기능 수행이 완료되어 성공했는지 실패했는지에 대한 상태를 말하며, 다음 3가지의 상태를 갖는다.

① 대기(pending) : 진행 상태, Promise 객체가 생성되어 사용될 준비가 된 상태
promise의 객체는 new Promise()로 생성할 수 있으며, 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject이다.
new Promise(function(resolve, reject) {})

② 이행(Fulfilled) : 성공 상태, 비동기 처리에 의해 원하는 올바른 결과를 얻어와 그 결과를 정상적으로 처리하고자 resolve가 호출된 상태

③ 거부(Rejected) : 실패 상태, 무언가 잘못되어 예외로 처리하고자 reject가 호출된 상태

const promise = new Promise((resolve, reject) => { // 대기 상태
  getData(
    response => resolve(response.data),			   // 이행 상태
    error => reject(error.message)        		   // 거부 상태
  )
})

• promise 메서드 체인 (then/catch/finally)

  • 체이닝(chaining) : 동일한 객체에 메서드를 연결할 수 있는 것

• then() - 성공(resolve) 시에는 then 메서드에 실행할 콜백 함수를 인자로 넘긴다.
• catch() - 실패(reject) 시에는 catch 메서드에 실행할 콜백 함수를 인자로 넘긴다.
• finally() - 성공/실패 여부와 상관없이 모두 실행 시에는 finally 메서드에 실행할 콜백 함수를 인자로 넘긴다.

promise
  .then(data => console.log(data))
  .catch(err => console.error(err))
  .finally(() => console.log("always run")

위와 같이 메서드를 체이닝하면 함수를 호출한 주체가 실행을 완료한 뒤 자기 자신을 리턴한다.

4. Fetch API

Fetch 함수는 자바스크립트에서 서버로 네트워크 요청을 보내고 응답을 받을 수 있도록 해준다. Promise 기반으로 구성되어 있어 비동기 처리에 편리하다. 따라서 Promise의 후속 처리 메서드인 then이나 catch와 같은 체이닝으로 작성할 수 있다.

// fetch 기본 구조
fetch(url, options)  // url, options
  .then(response => response.json())
  // 첫 번째 then에서는 데이터 타입을 결정한다.
  .then(data => { console.log(data) })	
  // 두 번째 then에서는 데이터를 전달 받는다.
  .catch(error => { console.log("error") });
  // catch에서 에러 요청이 발생했을 때, 에러를 받는다.

보이는 것과 같이 Promise 객체와 기본적인 구조와 동작은 비슷하다.

첫번째 인자로 URL, 두번째 인자로 옵션 객체를 받고 Promise 타입의 객체를 반환한다. 반환된 객체는, API 호출이 성공했을 경우에는 응답(response) 객체를 resolve하고, 실패했을 경우에는 예외(error) 객체를 reject한다.

코드를 좀 더 자세히 뜯어보자면,
fetch의 첫 번째 인자에 요청할 url이 들어가고, 두 번째 인자에는 옵션이 들어간다. 기본적으로 http 메소드 중 GET으로 동작하며, fetch를 통해 ajax를 호출 시 해당 주소에 요청을 보낸 다음, 응답을 받게 된다.
응답을 받은 후에, 첫 번째 then에서는 response.json() 메서드로 JSON 형태로 파싱하여 리턴한다. (이 때 response.text()로 작성하면 텍스트 형태로 리턴한다.)
그 다음 두 번째 then에서 리턴 받은 json 값을 받고, 원하는 처리 한다.
마지막으로 catch에서는 에러 처리를 한다.

5. async/await

then()을 연쇄적으로 체이닝하다보면 콜백지옥마냥 혼란에 빠질 수 있다. 이러한 문제를 해결하기 위해 탄생한 것이 async/await이다.

즉, async/await를 사용하면 기존의 Promise를 보다 간결하게 작성할 수 있다.
따라서 async/await는 비동기 코드를 동기적인 코드인 것처럼 직관적으로 바꿔주는 역할을 한다. → 비동기 코드에 순서를 부여

• await는 반드시 async 함수 안에서 실행되며, async는 항상 Promise 객체를 반환한다.

// 기본 구조
async function() {
  await
}

• then이 하던 작업을 await가 대신한다. 따라서 await 키워드는, then을 체이닝한 것처럼 순서대로 동작한다.

async function asyncFunc() {
 	let response = await fetch('#');
	let data = await response.json();
    return data;
}

• try-catch 구문을 사용하여 에러 처리를 할 수 있다는 것도 장점이다.

async function asyncFunc() {
  try {  // try 안에 실행될 코드를 넣어주면 됨
 	let response = await fetch('#');
	let data = await response.json();
    return data;
  } catch (e) {  // catch에는 에러시 실행될 코드
    console.log("error : ", e)
  }
}
profile
기록하고 공유하려고 노력하는 DevOps 엔지니어

0개의 댓글