비동기 프로그래밍은 현대 소프트웨어 개발에서 중요한 부분입니다. Dart 언어는 비동기 프로그래밍을 간결하게 작성할 수 있는 다양한 기능을 제공합니다. 이 글에서는 Dart 언어의 Async/Await 개념과 이러한 비동기 함수 작성 방법에 대해 자세히 알아보겠습니다.
1. 비동기 프로그래밍 개요
비동기 프로그래밍은 프로그램이 특정 작업이 완료되기를 기다리지 않고 다른 작업을 수행할 수 있도록 하는 프로그래밍 스타일입니다. 전통적으로 프로그래밍은 순차적으로 실행되므로, 느린 작업(예: 네트워크 요청, 파일 시스템 접근 등)이 완료될 때까지 다른 작업이 중단됩니다. 비동기 프로그래밍은 이러한 문제를 해결하여 더욱 효율적인 코드를 작성할 수 있도록 합니다.
Dart는 비동기 프로그래밍을 지원하는 여러 가지 메커니즘을 제공합니다. 그 중 가장 널리 사용되는 것은 Future와 Stream이며, 이들을 통해 비동기 작업을 수행할 수 있습니다. 비동기 프로그래밍을 사용하면 UI가 중단되지 않고, 사용자 경험을 향상시킬 수 있습니다.
2. Future란?
Dart에서 Future는 비동기 작업의 완료를 나타내는 객체입니다. Future 객체는 나중에 사용 가능한 값을 나타낼 수 있으며, 이 값이 준비되면 Future는 완료 상태가 됩니다. Future는 성공적으로 완료되거나 오류가 발생할 수 있습니다.
예를 들어, 다음과 같이 간단한 비동기 함수가 있습니다.
Future fetchData() {
return Future.delayed(Duration(seconds: 2), () => '데이터 로드 완료');
}
위 함수는 2초 후에 문자열을 반환하는 Future를 생성합니다. 이를 호출하면, 항상 Future 객체가 반환됩니다.
3. Async/Await의 개념
이제 비동기 프로그래밍에서 사용되는 async와 await 키워드에 대해 알아보겠습니다. async는 함수에 추가하여 해당 함수가 비동기적으로 실행됨을 나타냅니다. await는 Future가 완료될 때까지 기다리도록 만들어주는 키워드입니다. 이 두 키워드를 사용하면 비동기 코드를 보다 명확하게 작성할 수 있습니다.
async와 await를 사용한 예제는 다음과 같습니다.
Future main() async {
print('데이터 로드 중...');
String data = await fetchData();
print(data);
}
위 코드에서 main() 함수는 async로 선언되어 있으며, fetchData()를 호출하고 그 결과를 기다립니다. 이렇게 하면 처리가 끝나기 전까지 다른 코드를 블로킹하지 않으면서도, 코드가 순차적으로 실행되는 것처럼 작성할 수 있습니다.
4. 비동기 함수 작성 방법
비동기 함수를 작성하기 위해서는 다음의 절차를 따르면 됩니다.
- 함수 앞에 async 키워드를 붙입니다.
- Future 객체를 반환하도록 하며, 필요한 경우 await 키워드를 사용합니다.
- Future가 완료될 때까지 기다립니다.
예제 코드를 통해 자세히 살펴보겠습니다.
Future fetchDataFromAPI() async {
var response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('데이터 로드 실패');
}
}
Future main() async {
try {
String data = await fetchDataFromAPI();
print('로드된 데이터: $data');
} catch (e) {
print('오류 발생: $e');
}
}
위 예시에서 fetchDataFromAPI 함수는 비동기적으로 HTTP 요청을 보내고 데이터를 가져옵니다. HTTP 요청이 완료될 때까지 기다리는 코드가 await 키워드를 통해 자연스럽게 작성됩니다. 성공적인 응답을 처리할 수도 있고, 오류가 발생한 경우 try-catch 구문을 통해 예외를 처리합니다.
5. 비동기 작업의 예외 처리
비동기 프로그래밍에서는 예외 처리가 중요한 부분입니다. await와 함께 사용되는 비동기 함수는 예외가 발생할 수 있으므로, try-catch 블록을 통해 이러한 예외를 안전하게 처리할 수 있습니다. 예를 들어, 위의 예제에서 catch 블록을 통해 오류 상황을 처리하는 방법을 볼 수 있습니다.
예외 처리의 추가적인 예시는 다음과 같습니다:
Future processData() async {
try {
String data = await fetchDataFromAPI();
// 데이터 처리 로직...
} catch (e) {
print('비동기 작업 중 오류 발생: $e');
}
}
이와 같이, 비동기 작업을 수행하는 함수 내에서 예외를 처리하여 프로그램 흐름을 안전하게 유지할 수 있습니다.
6. Stream과 비동기 프로그래밍
Stream은 Dart의 또 다른 비동기 프로그래밍 메커니즘입니다. Stream은 연속된 데이터를 대량으로 처리하는 데 유용하며, 데이터의 흐름을 비동기적으로 처리할 수 있습니다.
다음은 Stream을 사용하는 간단한 예제입니다:
Stream countStream() async* {
for (int i = 1; i <= 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i; // 데이터를 방출
}
}
Future main() async {
await for (var count in countStream()) {
print('카운트: $count');
}
}
위의 countStream 함수는 1초 간격으로 1부터 5까지의 숫자를 방출합니다. main 함수는 Stream에서 값을 읽으며, 각 값이 방출될 때마다 출력합니다.
7. 비동기 프로그래밍 모범 사례
비동기 프로그래밍을 사용할 때 몇 가지 모범 사례를 따르는 것이 중요합니다:
- 명확한 함수 이름을 사용하여 함수의 비동기적 동작을 명확히 합니다. 예를 들어,
fetchData
보다는fetchDataAsync
처럼 붙여주면 코드의 가독성을 높일 수 있습니다. - 모든 비동기 함수는 적절하게 예외 처리를 구현하여 오류 상황을 안전하게 처리합니다.
- 필요 시
Future
와 같은 반환 타입을 사용하여 명시적으로 확인합니다. - Stream을 사용하여 데이터의 흐름을 관리할 경우, 데이터의 시작과 종료를 명확히 관리합니다.