비동기 프로그래밍의 혁신: JavaScript Async/Await 완벽 가이드

작성일 :

JavaScript 비동기 프로그래밍의 혁신: Async/Await 완벽 가이드

JavaScript는 본래 단일 스레드 언어로 설계되었지만, 웹 애플리케이션의 복잡성이 증가하면서 비동기 프로그래밍이 필수 요소로 떠올랐습니다. 전통적으로 콜백 함수(callback function)프로미스(Promise)를 사용해 비동기 작업을 처리했지만, 이는 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만들었습니다. 이를 해결하기 위해 ECMAScript 2017(ES8)에서는 async/await 키워드가 도입되었습니다. 이번 가이드에서는 async/await의 기본 개념부터 고급 사용법까지 다뤄보겠습니다.

기본 개념

asyncawait는 프로미스를 보다 직관적이고 간결하게 처리할 수 있게 해주는 키워드입니다. 비동기 함수를 정의할 때 async 키워드를 사용하며, 해당 함수 내에서 프로미스를 처리할 때 await 키워드를 사용합니다.

async 함수

async 키워드는 함수가 항상 프로미스를 반환하도록 만듭니다. 예를 들어, 다음은 간단한 async 함수 입니다:

javascript
async function fetchData() {
  return 'Hello, Async!';
}

위 함수는 호출시 자동으로 프로미스를 반환합니다. fetchData 함수는 값을 직접 반환하지만, 이는 내적으로 Promise.resolve('Hello, Async!')로 변환됩니다. 따라서 우리는 다음과 같이 이 값을 처리할 수 있습니다:

javascript
fetchData().then(data => console.log(data)); // Hello, Async!

await 키워드

await 키워드는 async 함수 내에서만 사용할 수 있습니다. 이는 특정 프로미스가 해결될 때까지 함수 실행을 일시 중지하고, 해결된 값을 반환합니다. 다음은 await 키워드를 사용하는 예제입니다:

javascript
async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

여기서 fetchresponse.json()은 모두 프로미스를 반환합니다. await 키워드는 이들 프로미스가 해결될 때까지 기다린 후 그 결과를 반환합니다.

에러 처리

async/await를 이용한 에러 처리는 매우 간단하지만, 중요한 부분입니다. 일반적으로 try/catch 블록을 사용합니다:

javascript
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch error:', error);
    throw error; // 필요 시 에러를 다시 던질 수 있습니다.
  }
}

복수의 비동기 작업 처리

때때로 복수의 비동기 작업을 병렬로 처리해야 할 때가 있습니다. 이 경우 Promise.allawait를 함께 사용할 수 있습니다:

javascript
async function fetchMultipleData() {
  const [data1, data2] = await Promise.all([
    fetch('https://api.example.com/data1').then(response => response.json()),
    fetch('https://api.example.com/data2').then(response => response.json()),
  ]);
  return [data1, data2];
}

Promise.all은 배열 내 모든 프로미스가 해결될 때까지 기다린 후 결과를 배열로 반환합니다. 이 방법을 사용하면 네트워크 요청을 병렬로 처리하여 성능을 최적화할 수 있습니다.

async/await의 실용적인 사례

async/await는 다양한 실제 상황에서 유용하게 사용될 수 있습니다. 그릇된 시나리오 몇 가지를 살펴보겠습니다.

데이터베이스 쿼리

백엔드(Node.js)에서 데이터베이스 쿼리를 처리할 때 async/await는 코드를 단순하고 깔끔하게 만들어줍니다:

javascript
const fetchUserData = async (userId) => {
  try {
    const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
    return user;
  } catch (error) {
    console.error('Database query error:', error);
    throw error;
  }
};

파일 읽기/쓰기

Node.js에서 파일 시스템 작업을 처리할 때도 async/await는 큰 도움이 됩니다:

javascript
const fs = require('fs').promises;

const readFile = async (filePath) => {
  try {
    const data = await fs.readFile(filePath, 'utf-8');
    return data;
  } catch (error) {
    console.error('File read error:', error);
    throw error;
  }
};

결론

async/await는 JavaScript 비동기 프로그래밍의 강력한 도구입니다. 이를 사용하면 복잡한 비동기 코드를 단순하고 직관적으로 작성할 수 있으며, 에러 처리도 훨씬 간편하게 할 수 있습니다. 익숙해지기까지 시간이 조금 걸릴 수 있지만, 한 번 숙달되면 코드 가독성과 유지보수성에서 확실한 이점을 얻을 수 있습니다.

이 가이드를 통해 async/await의 기본 개념과 고급 사용법을 익히며, 비동기 프로그래밍을 보다 효율적으로 다룰 수 있기를 바랍니다.