Skip to content

JavaScript 비동기 프로그래밍 및 콜백

프로그래밍 언어의 비동기성

컴퓨터는 설계상 비동기적입니다.

비동기적이라는 것은 주 프로그램 흐름과 독립적으로 일이 발생할 수 있다는 것을 의미합니다.

현재 소비자 컴퓨터에서 모든 프로그램은 특정 시간 동안 실행된 다음 실행을 중단하여 다른 프로그램이 실행을 계속할 수 있도록 합니다. 이 작업은 너무 빠르게 순환적으로 실행되어 눈치채기 어렵습니다. 컴퓨터가 여러 프로그램을 동시에 실행한다고 생각하지만 이는 착각입니다(멀티프로세서 머신 제외).

프로그램은 내부적으로 시스템의 주의를 끌기 위해 프로세서로 방출되는 신호인 인터럽트를 사용합니다.

지금은 이것의 내부 구조에 대해 자세히 설명하지 않겠지만, 프로그램이 비동기적이고 주의가 필요할 때까지 실행을 중단하는 것이 정상이며, 그 동안 컴퓨터가 다른 작업을 실행할 수 있다는 점만 명심하십시오. 프로그램이 네트워크에서 응답을 기다리는 동안 요청이 완료될 때까지 프로세서를 중단할 수 없습니다.

일반적으로 프로그래밍 언어는 동기적이며 일부는 언어 자체 또는 라이브러리를 통해 비동기성을 관리하는 방법을 제공합니다. C, Java, C#, PHP, Go, Ruby, Swift 및 Python은 모두 기본적으로 동기적입니다. 그 중 일부는 스레드를 사용하거나 새 프로세스를 생성하여 비동기 작업을 처리합니다.

JavaScript

JavaScript는 기본적으로 동기적이며 단일 스레드입니다. 즉, 코드가 새 스레드를 생성하여 병렬로 실행할 수 없다는 의미입니다.

코드 줄은 다음과 같이 순서대로 하나씩 실행됩니다.

js
const a = 1;
const b = 2;
const c = a * b;
console.log(c);
doSomething();

그러나 JavaScript는 브라우저 내부에서 탄생했으며 처음에는 onClick, onMouseOver, onChange, onSubmit 등과 같은 사용자 작업에 응답하는 것이 주요 작업이었습니다. 동기 프로그래밍 모델로 어떻게 이를 수행할 수 있었을까요?

정답은 환경에 있었습니다. 브라우저는 이러한 종류의 기능을 처리할 수 있는 API 세트를 제공하여 이를 수행하는 방법을 제공합니다.

최근에 Node.js는 이러한 개념을 파일 액세스, 네트워크 호출 등으로 확장하기 위해 논블로킹 I/O 환경을 도입했습니다.

콜백

사용자가 언제 버튼을 클릭할지 알 수 없습니다. 따라서 클릭 이벤트에 대한 이벤트 핸들러를 정의합니다. 이 이벤트 핸들러는 이벤트가 트리거될 때 호출될 함수를 허용합니다.

js
document.getElementById('button').addEventListener('click', () => {
  // 항목 클릭됨
});

이것을 소위 콜백이라고 합니다.

콜백은 다른 함수에 값으로 전달되고 이벤트가 발생할 때만 실행되는 단순한 함수입니다. JavaScript에는 변수에 할당하고 다른 함수(고차 함수라고 함)에 전달할 수 있는 일급 함수가 있기 때문에 이 작업을 수행할 수 있습니다.

클라이언트 코드를 모두 window 객체의 load 이벤트 리스너로 래핑하는 것이 일반적이며, 페이지가 준비될 때만 콜백 함수를 실행합니다.

js
window.addEventListener('load', () => {
  // 창 로드됨
  // 원하는 작업 수행
});

콜백은 DOM 이벤트뿐만 아니라 모든 곳에서 사용됩니다.

일반적인 예는 타이머를 사용하는 것입니다.

js
setTimeout(() => {
  // 2초 후 실행됨
}, 2000);

XHR 요청은 콜백을 허용하며, 이 예에서는 특정 이벤트가 발생할 때(이 경우 요청 상태가 변경될 때) 호출될 속성에 함수를 할당합니다.

js
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    xhr.status === 200 ? console.log(xhr.responseText) : console.error('error');
  }
};
xhr.open('GET', 'https://yoursite.com');
xhr.send();

콜백에서 오류 처리

콜백으로 오류를 어떻게 처리합니까? 매우 일반적인 전략 중 하나는 Node.js가 채택한 것을 사용하는 것입니다. 모든 콜백 함수의 첫 번째 매개변수는 오류 객체입니다. 오류 우선 콜백이라고 합니다.

오류가 없으면 객체는 null입니다. 오류가 있으면 오류에 대한 일부 설명 및 기타 정보가 포함되어 있습니다.

js
const fs = require('node:fs');
fs.readFile('/file.json', (err, data) => {
  if (err) {
    // 오류 처리
    console.log(err);
    return;
  }
  // 오류 없음, 데이터 처리
  console.log(data);
});

콜백의 문제점

콜백은 간단한 경우에는 좋습니다!

하지만 모든 콜백은 중첩 레벨을 추가하며, 콜백이 많으면 코드가 매우 빠르게 복잡해지기 시작합니다.

js
window.addEventListener('load', () => {
  document.getElementById('button').addEventListener('click', () => {
    setTimeout(() => {
      items.forEach(item => {
        // 여기에 코드 작성
      });
    }, 2000);
  });
});

이것은 단지 4단계의 간단한 코드이지만, 훨씬 더 많은 수준의 중첩을 본 적이 있으며 재미있지 않습니다.

이것을 어떻게 해결할 수 있을까요?

콜백의 대안

ES6부터 JavaScript는 콜백을 사용하지 않고 비동기 코드를 돕는 여러 기능을 도입했습니다: Promises(ES6) 및 Async/Await(ES2017).