Dart asynchronous programming

Byeonggwan Kang·2023년 4월 17일
0

dart, flutter and modes

목록 보기
9/9

Dart에서 비동기 처리를 어떻게 하는지 설명한다. (비동기가 뭔지에 대해선 다른 글로)

Dart 공식 비동기 튜토리얼(https://dart.dev/codelabs/async-await)을 보고 한글로 요약했습니다.

왜 비동기가 필요한가?

Dart에서도 다른 언어와 마찬가지로 언제 끝날지 모르는 연산들을 비동기로 처리한다. 예시는 다음과 같다.

  • 네트워크 환경에서 데이터를 가져온다.
  • 데이터베이스에 쓰기 명령을 내린다.
  • 파일로부터 데이터를 읽는다.

위와 같은 비동기적 처리를 진행하는 함수에서 Dart는 return 값으로 Future와 Stream이라는 클래스 자료형을 가진다.

// This example shows how *not* to write asynchronous Dart code.

String createOrderMessage() {
  var order = fetchUserOrder();
  return 'Your order is: $order';
}

Future<String> fetchUserOrder() =>
    // Imagine that this function is more complex and slow.
    Future.delayed(
      const Duration(seconds: 2),
      () => 'Large Latte',
    );

void main() {
  print(createOrderMessage());
}


출력
Your order is: Instance of '_Future<String>'

다음과 같이 비동기 처리를 위해서 2초간 텀을 두고 Large Latte를 반환하도록 의도한 코드에서도 그저 비동기 처리가 완료되지 않은 future 인스턴스를 반환한다는 것을 알 수 있다. 결과값은 2초 텀 없이 바로 나오는데, 이는 createOrderMessage가 비동기 처리를 기다려주지 않는 일반 함수이기 때문이다.

Future

따라서 우리는 Future와 그 값을 어떻게 기다릴 수 있는지 알아야 할 필요가 있다. Dart에서 future 인스턴스는 비동기 연산의 결과로, 완료되지 않은(uncompleted) 상태와 완료된(completed) 상태, 두 가지 상태를 가진다. 여기서 완료했을 때 T라는 자료형을 갖는 비동기 함수는 Future<T>를 반환하며 비동기 처리가 정상적으로 진행되지 않았을 때 error 값을 가질 수 있다.

Future<void> fetchUserOrder() {
  // Imagine that this function is fetching user info from another service or database.
  return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
}

void main() {
  print(fetchUserOrder());
  print('Fetching user order...');
}


출력
Instance of '_Future<void>'
Fetching user order...
(2초 후)
Large Latte

main 함수를 보면 fetchUserOrder 함수를 실행한 직후엔 아직 완료되지 않은 future 인스턴스가 출력됐다. 그리고 2초 딜레이 이후 원하는 대로 Large Latte가 출력되는 걸 알 수 있다.

Async and await

물론 Dart에서도 delay말고 비동기 처리가 안에 하나라도 존재한다는 의미의 async와 비동기 처리를 기다려줄 수 있는 수단인 await 키워드가 존재한다. 단순하게 await을 사용하려는 함수 몸통 앞에 async를 선언하면 된다.

// 정상 완료시 String을 반환하는 비동기 함수
Future<String> func() async { ··· }

// func이 반환하는 future 인스턴스가 완료된 후 출력하는 함수
print(await func());

아까 코드 예제를 깜찍하게 바꾸면 다음과 같다.

Future<String> createOrderMessage() async {
  var order = await fetchUserOrder();
  return 'Your order is: $order';
}

Future<String> fetchUserOrder() =>
    // Imagine that this function is
    // more complex and slow.
    Future.delayed(
      const Duration(seconds: 2),
      () => 'Large Latte',
    );

Future<void> main() async {
  print('Fetching user order...');
  print(await createOrderMessage());
}


출력
Fetching user order...
(2초 후)
Your order is: Large Latte

Dart스럽게 사용하기 위해서 async 함수들은 Future<T> 클래스를 반환하도록 설계하는 것 같다. (error를 처리하기 위함이라고 생각한다.)

또 다른 예제

밑의 예제의 결과가 어떻게 나올지 예상해보자.

Future<void> printOrderMessage() async {
  print('Awaiting user order...');
  var order = await fetchUserOrder();
  print('Your order is: $order');
}

Future<String> fetchUserOrder() {
  // Imagine that this function is more complex and slow.
  return Future.delayed(const Duration(seconds: 4), () => 'Large Latte');
}

void main() async {
  countSeconds(4);
  await printOrderMessage();
}

// You can ignore this function - it's here to visualize delay time in this example.
void countSeconds(int s) {
  for (var i = 1; i <= s; i++) {
    Future.delayed(Duration(seconds: i), () => print(i));
  }
}

main 함수에서 countSeconds 함수를 실행하고 printOrderMessage 함수의 결과를 기다리게 했다. 비동기 함수에서 첫 번째 await이 나오는 이전 부분들은 모두 동기로 실행된다는 것을 알면 결과를 쉽게 이해할 수 있다.
countSeconds 함수는 i초 후 i를 출력하는 future 인스턴스를 만들고 printOrderMessage 함수는 4초 후 Large 머시깽이가 나오므로 출력은 다음과 같다

출력
Awaiting user order...
(1초 후)
1
(1초 후)
2
(1초 후)
3
(1초 후)
4
Your order is: Large Latte

에러 처리

async함수의 에러 처리는 try-catch를 사용한다.

try {
  print('Awaiting user order...');
  var order = await fetchUserOrder();
} catch (err) {
  print('Caught error: $err');
}

번외: Flutter에서 Future 사용은?

위와 같은 방법으로 사용할 수 있다. 하지만 future 값을 가져와서 widget의 변경에 사용할 땐 FutureBuilder 위젯을 사용할 수 있다.

이 외에도 공식 홈페이지에는 큐트한 예제들과 codelab이 있으니 가서 해보면 좋을 것 같다!!

0개의 댓글