본문 바로가기
Programming/4. JavaScript & React

013_자바스크립트의 비동기 처리 종류 + setTimeout()

by @sangseophwang 2022. 3. 31.

'자바스크립트의 비동기 처리 방식엔 어떤게 있을까요?'

- 최근 진행했던 기술면접에서 제대로 답변하지 못했던 질문이었다.
- 분명 공부했었고 다 안다고 생각했는데, 막상 질문이 들어왔을 때 제대로 답변하지 못했던 그 때의 나를 반성하며 제대로 비동기 처리 방식에 대해 정리해보고자 한다.
시작하기 전 다시 정리하는 동기 & 비동기

1. 동기
  동작 : 현재 실행 중인 코드가 끝나야 다음 코드를 실행 = 현재 실행중인 task가 종료할 때까지, 다음 task가 대기하는 방식
  장점 : 동기 처리는 코드를 순서대로 하나씩 실행하기 때문에, 실행 순서가 보장된다.
  단점 : 현재 실행중인 task가 종료될 때까지 다음 task가 실행이 안 된다는게 문제 = 블로킹(blocking)

2. 비동기
  동작 : 현재 실행 중인 코드가 완료되지 않아도, 다음 코드로 넘어간다.
  장점: 현재 실행중인 task가 완료되지 않아도, 다음 task를 실행하기 때문에 블로킹이 발생하지 않는다.
  단점: task의 실행 순서가 보장되지 않는다.

3. 비동기 처리가 필요한 이유
  자바스크립트는 한번에 하나의 task만 실행할 수 있는 Single Thread 언어이다. 그렇기 때문에 처리 시간이 걸리는 task를 실행하면 Blocking(작업 중단) 현상이 발생한다.

4. 비동기의 주요 사례
- 마우스, 키보드 입력 (Click, Keydown 등)
- 페이지 로딩 (DOMContentLoaded 등)
- 타이머 API (setTimeout 등)
- 애니메이션 API (requestAnimationFrame)
- fetch API
- AJAX

 

1. ajax (Asynchronous JavaScript And XML)

- 서버와 통신하기 위해 XMLHttpRequest 객체를 사용하는 것.
- JSON, XML, HTML 그리고 일반 텍스트 형식 등을 포함한 다양한 포맷을 주고 받을 수 있다.
- 비동기적으로 서버와 브라우저가 데이터를 교환할 수 있는 통신 방식을 의미한다.
- 서버로부터 웹페이지가 반환되면 화면 전체를 갱신해야 하는데, 페이지 일부만을 갱신하고도 동일한 효과를 볼 수 있도록 한다.
- 간단한 사용 방식은 다음과 같다.
// XMLHttpRequest 객체의 생성
const xhr = new XMLHttpRequest();

// 비동기 방식으로 Request를 오픈한다
xhr.open('GET', '/users');

// 오픈 사용 방식은 다음과 같다.
// XMLHttpRequest.open(method, url[, async])
// method : HTTP method(GET,POST,PUT,DELETE)
// url : 요청을 보낼 URL
// async: 비동기 조작 여부. 옵션으로 default는 true이며 비동기 방식으로 동작한다.

// Request를 전송한다
xhr.send();


// json으로 전송하는 경우
xhr.open('POST', '/users');

// 클라이언트가 서버로 전송할 데이터의 MIME-type 지정: json
xhr.setRequestHeader('Content-type', 'application/json');

const data = { id: 3, title: 'JavaScript', author: 'Park', price: 5000};

xhr.send(JSON.stringify(data));
동작 원리
1. 클라이언트에서 JS 함수를 통해 AJAX 요청을 한다.
2. XMLHttpRequest 객체의 인스턴스가 생성된다.
3. XMLHttpRequest를 통해 현재 HTML 상태를 가진 XML 메세지를 구성해 웹서버로 요청한다.
4. 웹서버에서 처리 후 응답값을 XML 메세지로 보내 XMLHttpRequest 객체가 수신한다.
5. 받은 XML 메세지를 파싱해 데이터를 업데이트한다.

 

2. Callback Function

- 예를 들어 (1)API를 사용해 다른 서버에 저장된 정보를 응답받았을 때 그 정보를 (2)내 DB에 저장하고 (3)메인 페이지로 이동하는 프로그램이 있다고 하자.
- 이 프로그램에서 기대한 기능은 저 실행 순서대로 작동하는 것인데, 문제는 API 호출을 했지만 언제 API 서버의 응답값을 받을지 모른다는 것이다.
- AJAX를 통해서 API 서버를 호출하게 되는데, 이는 비동기적 처리이기 때문에 서버에 요청한 후 응답을 기다리지 않고 다음 코드를 진행한다.
- 따라서 응답이 오지 않았는데 다음 코드인 메인 페이지로 이동하는 로직이 실행될 수 있다는 문제가 있다.
- 이에 비동기적 처리의 실행 순서를 제어할 필요가 있었고, 그 방법으로 콜백함수가 있다.
정의
함수의 매개변수가 함수일 때, 매개변수로 받은 함수

사용 방식
비동기 뿐만 아니라 동기 방식으로도 사용

장점
비동기 함수의 콜백 내부에서 다음 작업을 호출하기 때문에 비동기 처리의 실행 순서를 정할 수 있다.

단점
'콜백지옥' 이라고 불리는 현상이 발생할 수 있다.
(함수 안에 함수가 반복되어 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상)
(가독성이 떨어지고 코드 수정을 어렵게 한다.)
// sync callback - 1
function showMessage(msg, closeFn) {
	// 로직
	
	closeFn(true);
}

// sync callback - 2
[1,2,3].map(element => element * 2);


// async callback - 1
window.addEventListener('keydown', e => {
	// 로직
});

// async callback - 2
// setTimeout 함수에 넘긴 익명함수가 콜백함수다.
setTimeout(function(){ 
	alert("Hello"); 
}, 3000);

// async callback - 3
function order(request, callback) {
  console.log(`${request} 주문이 접수되었습니다.`);
  setTimeout(() => callback(request), 3000);
}

let request = '햄버거';

// callback으로 비동기 함수 전달
order(request, (response) => {
  console.log(`주문하신 ${response} 나왔습니다.`);
});

 

3. Promise

- ES6에 등장한 Promise는 콜백함수보다 비동기 처리의 순서를 파악하기 쉽다.
- Promise는 자바스크립트 비동기 처리에 사용되는 객체이며 비동기 작업이 완료된 이후 다음 작업을 연결시켜 진행하게 해준다.
- Promise에는 3가지 state가 있다.
 1) pending(대기 상태) : 프로미스가 만들어져 operation이 실행되는 상태
 2) fulfilled(성공 상태) : operation이 성공적으로 완료된 상태
 3) rejected(실패 상태) : 파일을 찾을 수 없거나 문제가 있는 상태
// Promise 예제 - 1 (값 전달 방식)
function getData() {
  return new Promise(function(resolve, reject) {
    const data = 100;
    resolve(data);
  });
}

// resolve()의 결과 값 data를 resolvedData로 받음
getData()
  .then(function(resolvedData) {
    console.log(resolvedData); // 100
});


// Promise 예제 - 2 (catch()로 오류 감지)
function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}

getData()
  .then(function(result) {
    console.log(result); // hi
    throw new Error("Error in then()");
}).catch(function(err) {
    console.log('then error : ', err); // then error :  Error: Error in then()
});


// Promise 예제 - 3 (Promise Chaining)
function getData() {
  return new Promise({
    // ...
  });
}

// then() 으로 여러 개의 프로미스를 연결한 형식
getData()
  .then(function(data) {
    // ...
  })
  .then(function() {
    // ...
  })
  .then(function() {
    // ...
  });

 

4. Async & Await

- Promise Chaining 방식으로 .then()을 연쇄적으로 호출하다보면 에러가 발생했을 때 몇번째 then에서 문제가 발생한건지 파악하기 어려워질 수 있다.
- 이러한 불편한 점들을 해결하기 위해 ES7(ES2017)에서 async/await 키워드가 추가됐다.
- 이 키워드를 사용한다면 비동기 코드를 마치 동기 코드처럼 보이게 작성해 가독성을 높일 수 있다.
- async/await을 사용하면 await이 대기를 처리해주기 때문에 .then이 거의 필요하지 않다. 또한 promise에서 사용하던 .catch 대신 일반 try/catch를 사용할 수 있다는 장점도 생긴다.
// async/await 활용 예시 - 1 (Promise)
async function foo() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000)
  });

  let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)

  alert(result); // "완료!"
}

foo();


// async/await 활용 예시 - 2 (fetch, try/catch)
async function foo() {
  try {
    let response = await fetch('http://유효하지-않은-주소');
    return response.data;
  } catch(error) {
    console.log(error); // failed to fetch
  }
}

foo();


// async/await 활용 예시 - 3 (throw new Error)
async function loadJson(url) { // (1)
  let response = await fetch(url); // (2)

  if (response.status == 200) {
    let json = await response.json(); // (3)
    return json;
  }

  throw new Error(response.status);
}

loadJson('no-such-user.json')
  .catch(alert); // Error: 404 (4)

 

부록. setTimeout()은 비동기 함수인가, 동기 함수인가?

- 정답은 비동기 함수다.
- setTimeout()은 함수 스택의 다른 함수 호출을 막지 않는다.
- 달리 말하자면, setTimeout()을 사용해 다음 함수 호출을 '일시정지'할 수 없다.
setTimeout(() => {console.log("첫 번째 메시지")}, 5000);
setTimeout(() => {console.log("두 번째 메시지")}, 3000);
setTimeout(() => {console.log("세 번째 메시지")}, 1000);

// 콘솔 출력:

// 세 번째 메시지
// 두 번째 메시지
// 첫 번째 메시지
반응형

댓글