웹개발/javascript

[자바스크립트] 비동기 처리

joamashi 2024. 8. 4. 02:55

비동기 처리란?

특정 작업이 완료될 때까지 기다리지 않고, 다른 작업을 먼저 수행하는 것을 의미합니다. 마치 밥을 짓는 동안 다른 집안일을 하는 것과 같습니다. 밥이 다 되면 알림을 받고 밥을 먹는 것처럼, 자바스크립트는 비동기 작업이 완료되면 알림을 받고 해당 작업을 처리합니다.

비동기 처리 방법

자바스크립트는 비동기 처리를 위해 다양한 방법을 제공합니다.

  • 콜백 함수: 가장 기본적인 방법으로, 비동기 작업이 완료될 때 호출될 함수를 미리 전달하는 방식입니다.
  • Promise: 비동기 작업의 결과를 나타내는 객체입니다. 콜백 함수보다 더욱 명확하고 간결하게 비동기 코드를 작성할 수 있습니다.
  • async/await: Promise를 사용하여 비동기 코드를 동기 코드처럼 작성할 수 있도록 해주는 문법입니다.

각 방법의 특징

  • 콜백 함수:
    • 간단하지만, 여러 개의 콜백 함수가 중첩될 경우 코드 가독성이 떨어지는 콜백 지옥 문제가 발생할 수 있습니다.
  • Promise:
    • 콜백 지옥 문제를 해결하고, then 메서드를 통해 연속적인 비동기 작업을 처리할 수 있습니다.
  • async/await:
    • Promise를 기반으로 하지만, 동기 코드처럼 자연스럽게 비동기 코드를 작성할 수 있습니다.
// 콜백 함수
function fetchData(callback) {
  setTimeout(() => {
    const data = { message: 'Hello, async!' };
    callback(data);
  }, 2000);
}

fetchData((data) => {
  console.log(data);
});

// Promise
function fetchDataPromise() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = { message: 'Hello, async!' };
      resolve(data);
    }, 2000);
  });
}

fetchDataPromise()
  .then((data) => {
    console.log(data);
  });

// async/await
async function fetchDataAsync() {
  const data = await fetchDataPromise();
  console.log(data);
}

fetchDataAsync();

왜 비동기 처리를 배워야 할까요?

  • 사용자 경험 향상: 비동기 처리를 통해 사용자 인터페이스의 응답성을 높이고, 더욱 부드러운 사용자 경험을 제공할 수 있습니다.
  • 복잡한 시스템 구축: 현대적인 웹 애플리케이션은 다양한 비동기 작업을 처리해야 합니다. 비동기 처리에 대한 이해는 이러한 시스템을 구축하는 데 필수적입니다.
  • Node.js: Node.js는 비동기 I/O 모델을 기반으로 하므로, Node.js 개발을 위해서는 비동기 처리에 대한 깊이 있는 이해가 필요합니다.

더 알아보기

  • Event Loop: 자바스크립트의 비동기 처리를 이해하기 위해서는 Event Loop에 대한 이해가 필요합니다.
  • Promise API: Promise 객체의 다양한 메서드와 활용 방법을 익히는 것이 좋습니다.
  • async/await: async/await를 사용하여 더욱 복잡한 비동기 로직을 구현하는 방법을 학습해 보세요.

콜백 함수 예시: 자세히 알아보기

콜백 함수는 다른 함수의 매개변수로 전달되어, 특정 작업이 완료된 후 실행되는 함수를 말합니다. 자바스크립트에서 비동기 처리를 구현하는 가장 기본적인 방법 중 하나입니다.

콜백 함수의 기본 구조

function 함수명(콜백) {
  // 비동기 작업 수행 (예: 네트워크 요청, 타이머)
  // 작업 완료 후 콜백 함수 호출
  콜백(결과값);
}

함수명((결과) => {
  // 결과를 이용한 작업
});

콜백 함수 예시

1. setTimeout

function greet(name) {
  console.log(`Hello, ${name}!`);
}

setTimeout(greet, 2000, 'world'); // 2초 후에 greet 함수 호출

2. 네트워크 요청

function fetchData(callback) {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'https://api.example.com/data');
  xhr.onload = () => {
    if (xhr.status === 200) {
      callback(JSON.parse(xhr.responseText));
    } else {
      callback(new Error('Network Error'));
    }
  };
  xhr.send();
}

fetchData((data) => {
  console.log(data);
});

3. 배열 메서드

const numbers = [1, 2, 3, 4, 5];

numbers.forEach((number) => {
  console.log(number * 2);
});

콜백 지옥 (Callback Hell)

콜백 함수를 여러 번 중첩해서 사용하면 코드 가독성이 떨어지고 유지보수가 어려워지는 문제가 발생합니다. 이를 콜백 지옥이라고 합니다.

function fetchData1(callback) {
  // ...
  callback((data1) => {
    fetchData2(data1, (data2) => {
      fetchData3(data2, (data3) => {
        // ...
      });
    });
  });
}

콜백 함수의 장단점

  • 장점:
    • 비동기 처리를 간단하게 구현할 수 있습니다.
    • 자바스크립트의 기본적인 기능입니다.
  • 단점:
    • 콜백 지옥 문제 발생 가능성이 높습니다.
    • 코드 가독성이 떨어질 수 있습니다.
    • 에러 처리가 어려울 수 있습니다.

Promise: 비동기 처리의 핵심

Promise는 자바스크립트에서 비동기 작업의 결과를 나타내는 객체입니다. 콜백 지옥 문제를 해결하고 코드 가독성을 높여 비동기 처리를 더욱 효율적으로 만들어줍니다.

Promise 생성하기

const promise = new Promise((resolve, reject) => {
  // 비동기 작업 수행
  setTimeout(() => {
    // 작업 성공 시
    resolve('성공');
  }, 2000);

  // 작업 실패 시
  // reject(new Error('에러 발생'));
});
  • resolve: 작업이 성공적으로 완료되었을 때 호출하는 함수입니다.
  • reject: 작업 중 오류가 발생했을 때 호출하는 함수입니다.

Promise 사용하기

promise
  .then((result) => {
    console.log(result); // '성공' 출력
  })
  .catch((error) => {
    console.error(error);
  });
  • then: Promise가 resolve되었을 때 실행되는 함수입니다.
  • catch: Promise가 reject되었을 때 실행되는 함수입니다.

Promise 체이닝

promise
  .then((result) => {
    return result + '!';
  })
  .then((result) => {
    console.log(result); // '성공!' 출력
  })
  .catch((error) => {
    console.error(error);
  });

then 메서드는 항상 새로운 Promise를 반환하므로 여러 개의 then 메서드를 연결하여 연속적인 비동기 작업을 처리할 수 있습니다.

Promise.all

여러 개의 Promise를 동시에 실행하고, 모든 Promise가 resolve되었을 때 결과를 처리합니다.

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    console.log(results); // 각 Promise의 결과값을 담은 배열
  })
  .catch((error) => {
    console.error(error);
  });

Promise.race

여러 개의 Promise 중 가장 먼저 resolve되거나 reject되는 Promise의 결과를 반환합니다.

Promise.race([promise1, promise2, promise3])
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });

async/await와 함께 사용하기

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

async/await: 더욱 간결하고 직관적인 비동기 처리

async/await는 Promise를 기반으로 하여 비동기 코드를 동기 코드처럼 작성할 수 있도록 해주는 자바스크립트 문법입니다. 콜백 지옥 문제를 해결하고 코드 가독성을 향상시켜 비동기 처리를 더욱 편리하게 만들어줍니다.

async 함수

  • 함수 앞에 async 키워드를 붙여 async 함수로 만듭니다.
  • async 함수 내부에서 await 키워드를 사용하여 Promise 객체의 결과를 기다릴 수 있습니다.

await 키워드

  • await 키워드 뒤에는 반드시 Promise 객체가 와야 합니다.
  • await 키워드가 실행되면 Promise가 resolve될 때까지 함수의 실행이 일시 중단됩니다.

예시

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

fetchData()
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error(error)
  })

 

위 코드는 다음과 같은 동작을 합니다.

  1. fetchData 함수가 호출됩니다.
  2. fetch 함수를 통해 데이터를 가져오는 Promise가 생성됩니다.
  3. await 키워드로 Promise가 resolve될 때까지 기다립니다.
  4. resolve된 값(response)을 response 변수에 할당합니다.
  5. response.json() 메서드를 호출하여 JSON 데이터를 파싱하는 또 다른 Promise를 생성합니다.
  6. await 키워드로 다시 한번 Promise가 resolve될 때까지 기다립니다.
  7. resolve된 값(data)을 data 변수에 할당하고 반환합니다.

다양한 비동기 처리 코드 예시

1. 콜백 함수를 이용한 간단한 예시

function fetchData(callback) {
  setTimeout(() => {
    const data = { message: 'Hello, async world!' };
    callback(data);
  }, 2000);
}

fetchData((data) => {
  console.log(data); // 2초 후에 "Hello, async world!" 출력
});

2. Promise를 이용한 예시

function fetchDataPromise() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { message: 'Hello, Promise!' };
      resolve(data);
    }, 2000);
  });
}

fetchDataPromise()
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error(error);
  });

3. async/await를 이용한 예시

async function fetchDataAsync() {
  const data = await fetchDataPromise();
  console.log(data);
}

fetchDataAsync();

4. fetch API와 async/await를 이용한 네트워크 요청

async function fetchUserData(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('There has been a problem with your fetch operation:', error);
  }
}

5. Promise.all을 이용한 여러 Promise 병렬 처리

const promise1 = new Promise(resolve => setTimeout(resolve, 1000, 'one'));
const promise2 = new Promise(resolve => setTimeout(resolve, 2000, 'two'));

Promise.all([promise1, promise2])
  .then(results => {
    console.log(results); // ['one', 'two']
  })
  .catch(error => {
    console.error(error);
  });

6. async/await와 for...of를 이용한 여러 비동기 작업 순차 처리

async function processData(data) {
  // 데이터 처리 로직
  return data * 2;
}

async function processArray(array) {
  const results = [];
  for (const item of array) {
    const result = await processData(item);
    results.push(result);
  }
  return results;
}

const numbers = [1, 2, 3, 4];
processArray(numbers)
  .then(results => console.log(results))
  .catch(error => console.error(error));

7. Node.js에서의 파일 읽기 (fs 모듈)

const fs = require('fs');

function readFileAsync(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}

readFileAsync('data.txt')
  .then(data => console.log(data))
  .catch(err => console.error(err));

8. React에서의 useEffect 훅을 이용한 데이터 Fetching

import { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      setData(data);
    };
    fetchData();
  }, []);

  return (
    <div>
      {data && <p>{data.message}</p>}
    </div>
  );
}

 

728x90