TIL16. 자바스크립트의 비동기 처리(콜백, 프로미스, async/await)

14667 단어 JavaScriptJavaScript

Today I Learned

오늘은 자바스크립트의 비동기처리 방식에 대해 공부하였다. 동기와 비동기의 차이, 비동기의 세 가지 처리방법에 대하여 배웠다.

동기와 비동기

동기

동기는 어떠한 작업이 동시에 일어나기 위한 조건을 뜻하고, 프로그래밍에서 어떠한 작업이 끝나기 전까지는 다음 작업이 진행되지 않는다는 말과 동일하다. 즉, 동기는 모든 코드가 순서를 가지고 진행되는 것을 뜻한다.

자바스크립트는 코드가 동기적으로 실행된다. 함수 콜스택이 하나만 존재하기 때문에 자바스크립트의 모든 함수들은 이전 함수가 끝날 때가지 기다렸다가 제어권이 넘어가면 그때 현재 함수를 실행하게 된다.

동기적 코드의 실행

비동기

비동기는 동기와는 달리, 특정 코드의 연산이 끝날 때까지 기다리지 않고 다음 코드를 실행하는 것을 뜻한다. 예를 들면,
1. AJAX를 통해 클라이언트에서 서버에 데이터를 요청
2. 클라이언트는 응답을 받을 때까지 기다리지 않고 다음 코드를 실행
3. 서버로부터 응답을 받았다면 응답을 받아 데이터를 처리
4. ...
이러한 과정을 통해 우리는 서버로부터 데이터를 전달받는다. 만약 이 모든 과정이 동기식으로 진행되었다면, 우리는 서버로 데이터를 요청하고 응답을 받을 때까지 다른 과정들이 실행되지 않아, 빈 브라우저를 보게 될 수도있다. 요청이 끝나기 전까지 다음 코드를 실행하지 않기 때문이다.

방금 자바스크립트는 동기적으로 진행된다고 했는데요?

맞다. 자바스크립트는 싱글스레드 언어로, 하나의 콜스택만을 가진다. 그러나 브라우저 자체는 멀티스레드로, event, ajax 등은 웹 api를 이용한다. 이러한 것들은 이벤트큐에 태스크가 전달되고, 콜스택이 비어있으면 하나씩 큐로부터 콜스택으로 옮겨 함수를 실행하게 된다.

자바스크립트에서 비동기 처리 방식

콜백 함수

자바스크립트를 사용하다보면, addEventListener, setTimeout, setInterval등을 많이 사용해 보았거나 많이 본 경험이 있을것이다. 이러한 함수들이 webApi로 인자로 콜백 함수를 받는다. 콜백 함수란, 특정 조건을 만족시켰을 때 실행되는 함수들을 뜻한다. 이러한 것들이 모두 비동기이다.

현재는 잘 사용하지 않지만, 이전에는 XMLHttpRequest라는 함수로 서버에 요청을 했었다. 이 코드는 요청에 성공적으로 응답을 받았을 때 실행되는 onSuccess, 요청에 실패했을 때 실행되는 onFail 콜백 함수를 인자로 받아서 실행했었다.

function request(url: string, onFulfilled: Function, onRejected: Function) {
  const xhr = new XMLHttpRequest();
  xhr.addEventListener("load", (e) => {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        // fulfilled
        onFulfilled(JSON.parse(xhr.responseText));
      } else {
        onRejected(xhr.statusText);
      }
    }
  });
  xhr.addEventListener("error", (e) => {
    onRejected(xhr.statusText);
  });
  xhr.open("GET", url);
  xhr.send();
}

export { request };

이는 성공시, 그리고 실패시 발생한 이벤트를 감지하여 콜백함수로 처리하는 과정을 거쳤다.
그런데 만약, 비동기 작업이 끝난 후 다음 비동기 작업을 처리하고, 그 다음 비동기 작업을 처리하는 과정이 반복된다면 어떤 식의 모양이 펼쳐질까?

콜백 지옥

정말 아름답다
이렇듯 콜백 함수로 비동기를 구현하는 것은 몇가지 단점이 있다.
1. 비동기 작업이 중첩되면 콜백 지옥이 형성된다.
2. 코드의 가독성이 떨어진다.
3. 수정 또는 디버깅이 어려워 유지보수에 문제가 있다.
4. 비즈니스 로직의 이해가 어려워진다.

이러한 자바스크립트로 비동기를 구현하는 과정중에 발생한 문제점을 해결하기 위하여 Promise체인이 나타나게 되었다.

Promise 체인

프로미스 체인은 비동기작업이 맞이할 미래의 완료 또는 실패와 그 값을 함께 반환하는 객체이다. resolvereject 를 인자로 받으며, promise 객체를 반환한다.

const promise = new Promise((resolve, reject) => {
  resolve(2)
})
.then((data) => {
  console.log(data)
  resolve(data) // return Promise.resolve(data)

이런식으로 사용할 수 있다.

goTowork((data) => data)
.then((data) => eatLunch(data))
.then((data) => goBackHome(data))
.catch((error) => console.log(error)); // catch error
// ...

콜백 함수 대신 프로미스를 이용하여 비동기를 구현할 경우, 단 하나의 뎁스로 비동기를 표현할 수 있어 가독성 부분이 매우 개선된다. 그리고 에러를 잡기 위해서는 각 체인마다 에러를 표현할 필요는 없고, 마지막에 에러를 잡기위한 .catch()를 작성해주면 된다.
단, 프로미스로 비동기를 구현하더라도 프로미스 체인이 길어지면 가독성이 떨어진다는 단점이 있다.

async/await

async/await는 이런 프로미스의 단점을 해결하였다. async/await는 우리가 평소에 동기적으로 코드를 작성하는 것과 동일하게 코드를 작성하면서도, 비동기적으로 어떤 것들을 처리할 수 있기 때문에 문법이 간단하다.

async function f(url) {
  const response = await fetch(url).then(response => response.json());
  return response;
}

console.log(f('some url'));

이때 return값으로 자동으로 Promise.resolve() 객체가 반환된다.
이때 에러의 캐치는 try...catch 에러 구문을 이용하여 에러를 처리할 수 있다.

async function f(url) {
  try {
    const response = await getResponse();
    setState((prev) => {
      return {
        ...prev,
        response
      }
    return response
    } catch(e) {
      console.log(e)
    }
}

모든 비동기 처리가 동일하겠지만, await가 많아질수록 대기하는 과정이 길어지므로 성능이 감소하기 때문에 이를 잘 신경써야한다.

추가 내용

Promise.Combinator에 대해 알아보기

마치며

오늘은 자바스크립트의 비동기 처리 방식에 대하여 배웠다. 처음 AJAX는 이벤트를 에밋하고 감지하는 방식으로 구현이 되어 있었음을 오늘 처음 알았다. 역시 사람은 꾸준히 배워야한다.
추가로 프론트엔드에서 컴포넌트를 만들 때 처음부터 api와 ui를 모두 작성하지 않고 먼저 Mock DB를 이용하여 컴포넌트를 만든 후, api를 연결한다고 한다. 나는 여태 반대로 했었고, 이래서 그런지 모든 작업이 정말 오래걸렸다 :< 그래도 앞으로는 이러한 과정을 거쳐 컴포넌트를 구현하는 연습을 해야겠다.

좋은 웹페이지 즐겨찾기