Introducing asynchronous JavaScript

김동현·2023년 3월 9일
0

자바스크립트

목록 보기
12/22

Synchronous programming

다음의 코드를 보자.

const name = 'Miriam';
const greeting = `Hello, my name is ${name}!`;
console.log(greeting);
// "Hello, my name is Miriam!"

브라우저는 코드가 작성된 순서대로 한 번에 한 줄씩 실행한다.

다음 줄로 가기 전에 현재 줄의 작업이 마칠 때까지 기다린다.

이것이 동기화 프로그램(synchronous program)이다.

다음과 같이 별도의 함수를 호출한 코드를 보자.

function makeGreeting(name) {
  return `Hello, my name is ${name}!`;
}

const name = 'Miriam';
const greeting = makeGreeting(name);
console.log(greeting);
// "Hello, my name is Miriam!"

여기서 makeGreeting() 은 synchronous 함수이다.

호출자(caller)가 makeGreeting() 함수의 모든 동작이 끝나고 값을 반환할 때까지 기다리기 때문이다.

The trouble with long-running synchronous functions

만약 synchronous 함수가 오래 걸리면 어떨까?

함수가 실행되는 동안 프로그램이 완전히 멈출 것이다.

사용자의 입력에 반응하지 않고, 클릭도 안되고 아무것도 하지 못할 것이다.

바로 이것이 synchronous 함수의 기본적인 문제이다.

우리가 원하는건 3가지다.

  1. 실행시간이 긴 함수를 호출.

  2. 프로그램이 멈추지 않고 계속 다른 이벤트에 대해 반응할 수 있도록, 아무리 실행시간이 긴 함수여도 호출하자마자 값을 반환.

  3. 실행시간이 긴 함수의 동작이 최종적으로 완료되면 그 결과를 알림.

이것들이 바로 asynchronous 함수가 하는 일이다.

Event handlers

위의 asynchronous 함수에 대한 설명을 보면 이벤트 핸들러가 생각날 수 있다.

실제로 이벤트 핸들러는 비동기 프로그래밍의 한 형태이다.

이벤트 핸들러는 바로 호출되는 것이 아닌, 이벤트가 일어날때 마다 호출된다.

만약 이벤트가 "비동기 작업이 끝났음"을 뜻하는 이벤트라면, 해당 이벤트는 caller에게 비동기 함수호출의 결과를 알려주는 데 사용될 수 있다.

일부 초기 비동기 API는 이러한 방식으로 이벤트를 사용했다.

예를 들어 보자.

XMLHttpRequest API는 자바스크립트를 사용해서 원격 서버에 HTTP 요청을 만들 수 있다.

이 작업은 시간이 오래 걸릴 수 있으므로 비동기 API이다.

따라서 XMLHttpRequest 객체에 이벤트 리스너를 연결함으로써 요청의 진행 및 완료에 대한 알림을 받을 수 있다.

다음 예제에서는 이 작업을 보여준다.

<button id="xhr">Click to start request</button>
<button id="reload">Reload</button>

<pre readonly class="event-log"></pre>
const log = document.querySelector('.event-log');

document.querySelector('#xhr').addEventListener('click', () => {
  log.textContent = '';

  const xhr = new XMLHttpRequest();

  xhr.addEventListener('loadend', () => {
    log.textContent = `${log.textContent}Finished with status: ${xhr.status}`;
  });

  xhr.open('GET', 'https://raw.githubusercontent.com/mdn/content/main/files/en-us/_wikihistory.json');
  xhr.send();
  log.textContent = `${log.textContent}Started XHR request\n`;});

document.querySelector('#reload').addEventListener('click', () => {
  log.textContent = '';
  document.location.reload();
});

Callbacks

이벤트 핸들러는 콜백의 특정 유형이다.

콜백 함수는 자동으로 적절한 시점에 호출될 것을 기대하며 다른 함수의 매개변수로 전달되는 함수이다.

위에서 본 것처럼, 콜백 함수는 자바스크립트에서 비동기 함수를 구현하는 주요 방식이었다.

그러나 콜백 기반의 코드는 콜백 함수가 또 다른 콜백 함수를 호출해야 하는 경우 개발자가 이해하기 어려울 수 있다.

다음의 예제를 보자.

function doStep1(init) {
  return init + 1;
}

function doStep2(init) {
  return init + 2;
}

function doStep3(init) {
  return init + 3;
}

function doOperation() {
  let result = 0;
  result = doStep1(result);
  result = doStep2(result);
  result = doStep3(result);
  console.log(`result: ${result}`);
}

doOperation();

위의 코드는 result 가 0에서 1씩 더해지는 작업을 동기적인 코드로 나타낸 것이다.

앞의 함수의 호출 결과인 result 를 함수로 전달하고 반환된 값으로 다시 result 을 수정한다.

즉, result는 앞의 함수에 의존적이다.

이를 콜백 함수로 구현하면 아래와 같다.

function doStep1(init, callback) {
  const result = init + 1;
  callback(result);
}

function doStep2(init, callback) {
  const result = init + 2;
  callback(result);
}

function doStep3(init, callback) {
  const result = init + 3;
  callback(result);
}

function doOperation() {
  doStep1(0, (result1) => {
    doStep2(result1, (result2) => {
      doStep3(result2, (result3) => {
        console.log(`result: ${result3}`);
      });
    });
  });
}

doOperation();

수정된 result 를 사용하기 위해서 콜백 함수 내부에서 다른 콜백 함수를 호출해야 하기 때문에, 읽기 어려운 doOperation() 가 만들어졌다.

이러한 코드를 콜백 지옥(Callback Hell) 또는 운명의 피라미드(Pyramid Of Doom)이라 불린다.

콜백 지옥은 오류를 처리하기가 매우 어렵다.

최상위 수준에서 한 번에 오류를 처리하는게 아니라 중첩된 각각의 수준에서 오류를 처리해야 하는 경우가 많다.

이러한 이유로 대부분의 모던 비동기 API에서는 콜백 함수버전을 사용하지 않는다.

대신에 Promise 를 비동기 프로그래밍의 기본으로 한다.

[참고] : MDN

profile
프론트에_가까운_풀스택_개발자

0개의 댓글