본문으로 바로가기

 

 

Callback, Promise, 그리고 async/await은 모두 JavaScript에서 비동기 작업을 다루는 방식입니다. 이번 글에서는 각각의 차이점에 대해 포스팅해 보겠습니다. 비동기 작업을 다루기 전에 동기와 비동기의 차이점부터 작성하였으니 참고해 주세요.

 

 

1. Synchronous, Asynchronous

- 동기 (Synchronous)

  • 동기 작업은 순차적으로 진행되며, 하나의 작업이 완료될 때까지 다음 작업이 시작하지 않습니다.
  • 동기 코드는 작업이 순서대로 진행되기 때문에 간단하게 이해할 수 있고, 디버깅하기 쉽습니다.
  • 하나의 작업이 끝날 때까지 다른 작업을 수행하지 않기 때문에 성능 문제가 발생할 수 있습니다. 특히 긴 시간이 걸리는 작업은 애플리케이션을 블록 시킬 수 있습니다.

- 비동기 (Asynchronous)

  • 비동기 작업은 작업을 백그라운드에서 *병렬로 실행하고, 결과가 나올 때까지 기다리지 않고 다른 작업을 계속할 수 있습니다.
  • 비동기 코드는 콜백 함수, Promise, async/await와 같은 메커니즘을 사용하여 비동기 작업을 다룹니다.
  • 비동기 방식은 주로 I/O 작업 (파일 읽기, 네트워크 요청)과 같이 시간이 오래 걸리는 작업에 사용되며, 애플리케이션이 블록 되지 않고 응답성을 유지할 수 있습니다.

 

*병렬 처리

비동기 작업을 백그라운드 스레드 또는 프로세스에서 처리할 수 있습니다. 이것은 메인 스레드와 별도의 작업 스레드 또는 프로세스에서 동시에 여러 작업을 수행하는 것을 의미합니다. 이로 인해 여러 작업이 동시에 진행되는 것처럼 보입니다. 

 

비동기 작업은 결과가 나올 때까지 메인 스레드를 차단하지 않습니다. 메인 스레드는 비동기 작업이 진행되는 동안 다른 작업을 계속 수행할 수 있습니다. 이것은 애플리케이션의 응답성을 향상하고 더 많은 작업을 동시에 처리할 수 있게 합니다.

 

예를 들어, 웹 애플리케이션에서 사용자가 파일을 업로드하는 작업을 고려해 봅시다. 파일 업로드는 시간이 많이 걸릴 수 있습니다. 동기적으로 업로드를 처리하면 사용자는 업로드가 완료될 때까지 다른 작업을 수행할 수 없습니다. 그러나 비동기적으로 업로드를 처리하면 사용자는 업로드가 진행되는 동안 애플리케이션을 계속 사용할 수 있으며, 업로드가 완료되면 알림을 받을 수 있습니다. 이렇게 하면 사용자 경험이 향상되며, 애플리케이션이 병렬로 다른 작업을 처리할 수 있게 됩니다.

 

백그라운드에서 병렬로 실행하는 방식은 여러 비동기 작업이 동시에 진행되는 것처럼 보이게 하고, 애플리케이션의 성능과 응답성을 향상시킵니다.

 

 

2. Callback

  • 콜백은 비동기 작업의 완료 시 실행할 함수를 전달하는 패턴입니다. 이 함수는 주로 다른 함수가 비동기 작업을 수행한 후 호출됩니다.
  • 콜백은 일반적으로 함수의 매개변수로 전달됩니다.
  • 콜백은 콜백 지옥(callback hell)과 같은 코드 가독성 및 유지보수 문제를 일으킬 수 있으며, 복잡한 비동기 작업을 다루기 어려울 수 있습니다.
function fetchDataFromServer(callback) {
  // 비동기 작업 수행
  setTimeout(function() {
    // 작업 완료 후 콜백 함수 호출
    callback('데이터 받음');
  }, 1000);
}

fetchDataFromServer(function(data) {
  console.log(data);
});

 

 

3. Promise

  • Promise는 ES6(ECMAScript 2015)에서 도입된 객체로, 비동기 작업을 더 구조화하고 관리하기 위한 방법을 제공합니다.
  • Promise는 *세 가지 상태를 가질 수 있으며, 비동기 작업의 성공 또는 실패를 처리하기 위한 then(), catch() 메서드를 제공합니다.
  • Promise는 체이닝(Chaining)을 통해 여러 비동기 작업을 연결하고 더 읽기 쉽고 관리하기 쉬운 코드를 작성할 수 있게 해 줍니다.
function fetchDataFromServer() {
  return new Promise(function(resolve, reject) {
    // 비동기 작업 수행
    setTimeout(function() {
      // 작업 성공 시 resolve 호출
      resolve('데이터 받음');
    }, 1000);
  });
}

fetchDataFromServer()
  .then(function(data) {
    console.log(data);
  })
  .catch(function(error) {
    console.error(error);
  });

 

*  Promise의 세 가지 상태

1) 대기(Pending) 상태

  • Promise 객체가 생성되고, 비동기 작업이 아직 완료되지 않은 초기 상태입니다.
  • 이 상태에서는 resolve 또는 reject 함수가 호출되지 않은 상태입니다.
const myPromise = new Promise(function(resolve, reject) {
  // 비동기 작업 수행
  // 아직 작업이 완료되지 않음
});

 

2) 이행(Fulfilled) 상태

  • 비동기 작업이 성공적으로 완료되어 Promise 객체가 결과 값을 반환하는 상태입니다.
  • 이때 resolve 함수가 호출되며, 결과 값이 전달됩니다.
const myPromise = new Promise(function(resolve, reject) {
  // 비동기 작업 성공 시
  resolve('성공적으로 완료된 결과');
});

 

3) 거부(Rejected) 상태

  • 비동기 작업이 실패하거나 오류가 발생한 경우 Promise 객체가 거부 상태가 됩니다.
  • 이때 reject 함수가 호출되며, 에러 정보가 전달됩니다.
const myPromise = new Promise(function(resolve, reject) {
  // 비동기 작업 실패 시
  reject('에러 발생');
});

 

 

4. async/await

  • async/await는 ES2017(ECMAScript 2017)에서 도입된 비동기 패턴으로, Promise를 더 간결하게 사용할 수 있게 해 줍니다.
  • async 함수는 비동기 작업을 수행하고 await 키워드를 사용하여 Promise의 완료를 기다립니다. 이로써 코드는 동기식처럼 보이면서도 실제로는 비동기 작업을 수행합니다.
  • try-catch 블록을 사용하여 에러 처리가 가능하며, 코드의 가독성을 높이고 콜백 지옥을 피할 수 있습니다.
async function fetchDataFromServer() {
  try {
    // 비동기 작업 수행
    const data = await new Promise(function(resolve, reject) {
      setTimeout(function() {
        // 작업 성공 시 resolve 호출
        resolve('데이터 받음');
      }, 1000);
    });
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

fetchDataFromServer();

 

Promise를 사용한 경우에는 .then() 및 .catch()를 명시적으로 사용해야 하며, 콜백 함수를 중첩하여 코드를 작성하는 경우가 발생할 수 있습니다. 반면, async/await를 사용하면 코드가 선언적이고 순차적으로 보이며 가독성이 더 뛰어납니다. 이 패턴은 Promise를 기반으로 하며, 비동기 작업을 보다 간결하게 처리할 수 있게 합니다. 

 

요약하면, Callback은 가장 기본적인 비동기 패턴이며, Promise는 보다 구조화된 비동기 코드를 작성할 수 있게 해 주며, async/await은 코드 가독성을 높이고 복잡성을 줄이는데 도움을 줍니다.