[자바스크립트] 비동기 처리
비동기 처리란?
특정 작업이 완료될 때까지 기다리지 않고, 다른 작업을 먼저 수행하는 것을 의미합니다. 마치 밥을 짓는 동안 다른 집안일을 하는 것과 같습니다. 밥이 다 되면 알림을 받고 밥을 먹는 것처럼, 자바스크립트는 비동기 작업이 완료되면 알림을 받고 해당 작업을 처리합니다.
비동기 처리 방법
자바스크립트는 비동기 처리를 위해 다양한 방법을 제공합니다.
- 콜백 함수: 가장 기본적인 방법으로, 비동기 작업이 완료될 때 호출될 함수를 미리 전달하는 방식입니다.
- 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)
})
위 코드는 다음과 같은 동작을 합니다.
- fetchData 함수가 호출됩니다.
- fetch 함수를 통해 데이터를 가져오는 Promise가 생성됩니다.
- await 키워드로 Promise가 resolve될 때까지 기다립니다.
- resolve된 값(response)을 response 변수에 할당합니다.
- response.json() 메서드를 호출하여 JSON 데이터를 파싱하는 또 다른 Promise를 생성합니다.
- await 키워드로 다시 한번 Promise가 resolve될 때까지 기다립니다.
- 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>
);
}