[Dart 언어강좌] 014. Async Await와 비동기 프로그래밍, 비동기 함수 작성 방법

비동기 프로그래밍은 현대 소프트웨어 개발에서 중요한 부분입니다. 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. 비동기 함수 작성 방법

비동기 함수를 작성하기 위해서는 다음의 절차를 따르면 됩니다.

  1. 함수 앞에 async 키워드를 붙입니다.
  2. Future 객체를 반환하도록 하며, 필요한 경우 await 키워드를 사용합니다.
  3. 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을 사용하여 데이터의 흐름을 관리할 경우, 데이터의 시작과 종료를 명확히 관리합니다.

비동기 프로그래밍은 Dart를 사용한 현대적인 개발에서 필수적인 기술입니다. Async/Await의 사용으로 코드의 가독성과 유지 관리가 쉬워지며, 프로그램의 성능을 향상시키는 데 기여합니다. 이 글이 Dart에서 비동기 프로그래밍을 이해하는 데 도움이 되길 바랍니다.

참고 자료: