비동기 이해하기 #2

비동기에 대해서 이해해보는 글입니다.


비동기 이해하기는 2편의 시리즈로 구성되어 있습니다.

  1. 비동기 이해하기 #1
  2. 비동기 이해하기 #2 (현재글)

들어가며

비동기 이해하기 #1 에 이어서 이번에는 비동기를 적용하는 방법을 알아봅시다.


자바스크립트로 비동기 해보기

자바스크립트의 비동기 방식은 대표적으로 3가지를 꼽을 수 있습니다.
콜백함수, Promise, async/await 방법입니다.


콜백함수

자바스크립트를 공부하다 보면 한 번씩 콜백지옥 이라는 단어를 들어보셨을 텐데요.
콜백지옥을 알아보기 전에 콜백함수가 무엇인지 알아보면, 콜백함수는 다른 코드의 인수로 넘기는 실행이 가능한 함수를 말합니다.
또는 어떠한 이벤트에 의해 호출되는 함수라고 할 수 있습니다.
일반적인 함수랑 문법적으로 다르지 않고 단지 호출 방식이 나중에 호출되어 콜백함수로 부릅니다. 예를 들어보면,

const plus = (a, b, callback) => {
  // a, b, callback 인자로 받는다.
  const sum = a + b;
  callback(sum);
};

plus(4, 3, (result) => {
  // console.log로 결과를 찍는 함수를 콜백함수로 넘긴다.
  console.log(result);
});
// 결과 7

그렇다면 콜백함수를 사용하는 이유는 무엇일까요?

const plus = (a, b) => {
  const sum = a + b;
  return sum;
};

const print = (result) => {
  console.log(result);
};

print(plus(1, 2));

이렇게 print 함수를 따로 써서 사용하면 되지 않을까 생각할 수도 있지만 이렇게 사용했을 때의 문제는 plus 함수가 완료되기 전까지는 print 함수가 실행이 안 되기 때문에 실행이 지연되어 코드의 효율성이 떨어지게 됩니다.

지금은 한 개인 콜백함수가 인자로 전달되는 숫자가 많아지면 들여쓰기가 그만큼 많아지는데 그런 코드 방식을 콜백지옥이라고 흔히 말합니다.


Promise 객체

위에서 쓰인 콜백함수 방식보다 좀 더 쉽게 비동기를 처리할 수 있게 나온 방법이 Promise 객체를 이용한 방법입니다.
Promise란 언젠간 사용하게 될 값을 생성해내는 객체입니다.
이런 Promise는 3가지의 상태를 갖습니다.

  • Fulfilled (해결된 값)
  • Rejected (값을 얻지 못하게 된 이유)
  • Pending (진행 중인 상태)
  • Settled → 결괏값이 성공 혹은 실패일 때 (Fulfilled, Rejected 둘 중 하나의 상태일 때)

간단하게 Promise 메서드를 호출하면 진행 중인 상태가 됩니다.

new Promise(); // Pending

콜백함수의 인자는 resolve, reject 입니다.

new Promise((resolve, reject) => {});

resolve를 실행하면 Fulfilled 상태가 됩니다. 여기서 .then()을 사용하여 반환 값을 받을 수 있습니다.

new Promise((resolve, reject) => {
  // Fullfilled
  resolve();
});

reject를 실행하면 값을 얻지 못한 Rejected 상태가 됩니다. 실패 이유는 .catch() 로 받을 수 있습니다.

new Promise((resolve, reject) => {
  // Rejected
  reject();
});

간단하게 사용해보면

const learnPromise = new Promise((resolve, reject) => {
  setTimeout(function () {
    resolve('성공입니다'); // 성공하면 resolve 호출
  }, 3000);
});

learnPromise.then((successMessage) => {
  console.log(`성공 메시지 :  ${successMessage}`);
});

3초 후에 성공 메시지를 볼 수 있을 것입니다.


async/await

ES2017에 async/await 구문이 등장했습니다.
Promise를 기반으로 만들어졌는데 더 깔끔하게 코드를 쓸 수 있게 해줍니다.
await 구문은 async 키워드가 쓰인 곳에서만 사용할 수 있습니다.
일반 함수에 쓰면 Syntax 오류가 납니다.

기본적인 예제를 들면서 해볼까요?
Hello World를 출력하는 함수를 만들어봅니다.

const helloWorld = () => {
  return 'Hello World';
};

여기에 async 함수를 붙여볼까요?

const helloWorldAsync = async () => {
  return 'Hello World';
};

console.log(helloWorldAsync()); // 값이 Promise 객체로 반환된다.

async 키워드를 붙이면 함수의 return 값이 ‘Hello World’ string 값이 아닌 Promise 객체로 반환됩니다.
Promise 객체로 반환된 값을 사용하기 위해서는 .then()을 사용해야 하는데요,

const helloWorldAsync = async () => {
  return 'Hello World Async';
};

helloWorldAsync().then(console.log);

await을 사용하면 더 쉽게 사용할 수 있습니다.
await 키워드는 Promise 반환하는 함수를 호출하는 데 사용할 수 있습니다.
이런 경우 결과를 동기적으로 받을 수 있습니다.

const helloWorldAwait = async () => {
  const awaitHelloWorld = await Promise.resolve('Hello World Await');
  return awaitHelloWorld;
};

helloWorldAwait().then(console.log);

awaitHelloWorld 의 결과가 Fulfilled 상태가 될 때까지 잠시 중단했다가 결과를 반환합니다.


마치며

쉽게 이해되지 않던 비동기 방식에 관해 글을 써봤는데요, 글을 쓰면서도 이해가 어려운 부분들이 아직 있지만 자료를 찾아보면서 정리해보고 또는 깊게 생각하지 않고 넘어갔던 부분들에 대해 공부할 수 있는 시간을 가져서 좋았습니다.
예제 위주로 작성하고 싶었지만, 문법적으로 정리한 느낌이 강해서 아쉬웠습니다.
다음번에 기회가 된다면 예제 위주의 글을 다뤄보고 싶습니다.
읽어주셔서 감사합니다.


참고문서


추천 글