안녕하세요, TypeScript에서 비동기 프로그래밍을 다루는 방법에 대해 알아보겠습니다. TypeScript는 JavaScript의 비동기 프로그래밍 모델을 기반으로 하면서, 정적 타입 시스템을 통해 비동기 코드의 안전성과 가독성을 더욱 향상시킵니다. 이번 글에서는 비동기 프로그래밍의 기본 개념부터 TypeScript에서의 활용 사례까지 살펴보겠습니다.
비동기 프로그래밍은 시간이 오래 걸리는 작업(예: 파일 읽기, 네트워크 요청)을 실행하는 동안 프로그램의 나머지 부분이 중단되지 않고 계속 실행될 수 있도록 설계된 프로그래밍 방식입니다. JavaScript에서는 이벤트 루프와 콜백 함수, Promise
, async/await
같은 비동기 처리 메커니즘을 사용합니다.
TypeScript에서는 이러한 비동기 메커니즘에 타입 안전성을 추가하여 보다 신뢰할 수 있는 코드를 작성할 수 있습니다.
Promise
는 비동기 작업의 완료 또는 실패를 나타내는 객체입니다. TypeScript에서는 Promise
에 반환 타입을 명시하여 예상치 못한 타입 관련 오류를 방지할 수 있습니다.
function fetchData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('데이터 로드 완료');
}, 1000);
});
}
fetchData().then((data) => {
console.log(data); // "데이터 로드 완료"
});
위 코드에서 fetchData
함수는 Promise<string>
을 반환하므로, then
메서드에서 data
가 string
타입임을 확신할 수 있습니다.
Promise
를 사용할 때는 에러를 처리하는 것이 중요합니다.
function fetchDataWithError(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('데이터 로드 실패'));
}, 1000);
});
}
fetchDataWithError()
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error(error.message); // "데이터 로드 실패"
});
async/await
는 Promise
를 더 간단하고 가독성 높은 방식으로 처리할 수 있도록 도와줍니다. async
함수는 항상 Promise
를 반환하며, await
키워드는 Promise
가 처리될 때까지 함수를 일시 중단합니다.
async function fetchDataAsync(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve('비동기 데이터 로드 완료');
}, 1000);
});
}
async function main() {
const data = await fetchDataAsync();
console.log(data); // "비동기 데이터 로드 완료"
}
main();
async/await
에서 에러를 처리하려면 try/catch
블록을 사용합니다.
async function mainWithError() {
try {
const data = await fetchDataWithError();
console.log(data);
} catch (error) {
if (error instanceof Error) {
console.error(error.message); // "데이터 로드 실패"
}
}
}
mainWithError();
TypeScript에서는 비동기 함수의 반환 타입을 명확히 정의해야 합니다. Promise<T>
형태로 반환 타입을 지정합니다.
interface User {
id: number;
name: string;
}
async function getUser(id: number): Promise<User> {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, name: '홍길동' });
}, 1000);
});
}
async function main() {
const user = await getUser(1);
console.log(user.name); // "홍길동"
}
main();
비동기 함수에서도 제네릭을 활용하면 다양한 타입을 처리할 수 있습니다.
async function fetchDataGeneric<T>(data: T): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(data);
}, 1000);
});
}
async function mainGeneric() {
const stringData = await fetchDataGeneric<string>('Hello');
console.log(stringData); // "Hello"
const numberData = await fetchDataGeneric<number>(42);
console.log(numberData); // 42
}
mainGeneric();
Promise.all
과 Promise.race
비동기 작업을 배열로 처리할 때 Promise.all
과 Promise.race
를 사용할 수 있습니다.
Promise.all
Promise.all
은 모든 Promise
가 완료될 때까지 기다린 후 결과를 배열로 반환합니다.
async function fetchMultipleData() {
const results = await Promise.all([
fetchDataGeneric('데이터 1'),
fetchDataGeneric('데이터 2'),
fetchDataGeneric('데이터 3')
]);
console.log(results); // ["데이터 1", "데이터 2", "데이터 3"]
}
fetchMultipleData();
Promise.race
Promise.race
는 가장 먼저 완료된 Promise
의 결과를 반환합니다.
async function fetchFirstData() {
const result = await Promise.race([
fetchDataGeneric('데이터 A'),
new Promise((resolve) => setTimeout(() => resolve('타임아웃 데이터'), 500))
]);
console.log(result); // "타임아웃 데이터"
}
fetchFirstData();
비동기 작업에서 에러를 누락하지 않도록 항상 catch
블록이나 try/catch
를 사용해야 합니다.
await
의 순차 실행await
는 순차적으로 실행되므로, 병렬 처리가 필요한 경우 Promise.all
을 사용하는 것이 더 효율적입니다.
// 비효율적인 방식
async function sequential() {
await fetchDataGeneric('Step 1');
await fetchDataGeneric('Step 2');
}
// 병렬 처리
async function parallel() {
await Promise.all([
fetchDataGeneric('Step 1'),
fetchDataGeneric('Step 2')
]);
}
TypeScript의 비동기 프로그래밍은 Promise
와 async/await
을 활용하여 비동기 작업을 안전하고 효율적으로 처리할 수 있도록 도와줍니다.
Promise
와 async/await
을 활용하여 코드 가독성을 높이고 비동기 작업을 체계적으로 처리하세요.Promise.all
과 Promise.race
를 사용해 병렬 작업을 최적화하세요.TypeScript로 더욱 강력하고 유지보수하기 쉬운 비동기 코드를 작성해 보세요! 🚀