N coding

Dart : async/await/Future 본문

Dart&Flutter

Dart : async/await/Future

NYWOO19 2022. 1. 20. 20:55

https://www.notion.so/Async-programming-aa88c871fa434e0eb3f6aa8dd28944f4

 

Async programming

Future

www.notion.so

Future

Dart에서는 async operation의 결과를 Future로 나타낸다.

이름에서도 느껴지듯이 미래 어떤 시점에 사용 가능한 값 또는 error를 나타내는 데 사용한다.

Future는 uncompleted , completed 두 가지 상태가 있다.

  • uncompleted : async 함수를 호출하면 uncompleted future를 리턴해준다. 이 future가 async operation이 끝나거나 에러를 리턴하는 걸 기다린다.
  • completed : async operation이 성공적으로 끝나면 future는 결과값 또는 error를 리턴한다.
Future fetchUserOrder() {
  // Imagine that this function is fetching user info from another service or database.
  return Future.delayed(Duration(seconds: 2), () => print('Large Latte'));
}

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

//https://dart.dev/codelabs/async-await#example-introducing-futures

handle completed value

then() 함수를 통해서 future가 완료되었을 때 실행할 callback을 등록할 수 있다.

Future<int> successor = future.then((int value) {
    // Invoked when the future is completed with a value.
    return 42;  // The successor is completed with the value 42.
  },
  onError: (e) {
    // Invoked when the future is completed with an error.
    if (canHandle(e)) {
      return 499;  // The successor is completed with the value 499.
    } else {
      throw e;  // The successor is completed with the error e.
    }
  });
//then에서 return한 error를 처리하지못하면 catchError에서 처리가능, 더 readabiltiy가 좋다.
Future<int> future = getFuture();
future.then((value) => handleValue(value))
      .catchError((error) => handleError(error));

Similar to the synchronous code, the error handler (registered with catchError) is handling any errors thrown by either foo or bar. If the error-handler had been registered as the onError parameter of the then call, it would not catch errors from the bar call.

만약 catchError나 onError 처럼 error handler가 없다면 error를 uncaught-error handler로 포워딩한다.

여러 개의 future handling

wait함수를 사용한다.

wait 함수는 Future 리스트를 받아서 모든 Future들이 complete 될 때 완료되는 future를 리턴해준다.

인자로 받은 Future 리스트 중 하나라도 error로 완료되면 wait함수가 리턴한 future도 error로 완료된다. 그 이후에 다른 Future가 error로 완료되도 무시된다.

모든 Future가 값으로 성공적으로 완료되면 그 결과들을 순서대로 List에 담아준다.

Waits for multiple futures to complete and collects their results.

Returns a future which will complete once all the provided futures have completed, either with their results, or with an error if any of the provided futures fail.

The value of the returned future will be a list of all the values that were produced in the order that the futures are provided by iterating futures.

If any future completes with an error, then the returned future completes with that error. If further futures also complete with errors, those errors are discarded.

timeout

timeout

어떤 Future가 얼마나 실행될 지 모를 때 timeout을 걸어야 할 때가 있다.

Time-out the future computation after timeLimit has passed.

Returns a new future that completes with the same value as this future, if this future completes in time.

If this future does not complete before timeLimit has passed, the onTimeout action is executed instead, and its result (whether it returns or throws) is used as the result of the returned future. The onTimeout function must return a T or a Future<T>.

If onTimeout is omitted, a timeout will cause the returned future to complete with a TimeoutException.

async, await

async 와 await 키워드를 통해 비동기 함수와 그 결과를 잘 사용할 수 있다.

  1. 비동기 함수의 동작을 멈춰서 Future의 상태가 completed가 될 때까지 기다리고 싶을 때는 await 키워드를 사용한다.
  2. **await 키워드를 사용하고 싶으면 async 키워드를 함수 바디에 붙여서 비동기 함수로 정의해야한다.**
  3. **async 키워드가 붙은 비동기 함수 안에서도 첫번째 await 키워드가 나오기 이전의 코드는 동기로 실행된다.**
Future<void> printOrderMessage() async {
  print('Awaiting user order...'); //3.
  var order = await fetchUserOrder(); //4. 6.
  print('Your order is: $order'); //7.
}

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

Future<void> main() async {
  countSeconds(4); //1.
  await printOrderMessage(); //2. 4.
  print('where am I?'); //8.
}

// 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));
  }
}
  1. countSeconds 함수를 동기로 수행한다. countSeconds안에서 4개의 future를 queue한다.
  2. printOrderMessege를 호출한다.
  3. await 키워드를 만나기 전까진 동기니까 'Awaiting user order...' 가 출력된다.
  4. fetchUserOrder를 호출한다. await 키워드가 붙어있으므로 main으로 Future<void>를 리턴한다. 하지만 main에도 await 키워드가 붙어있으므로 리턴된 Future<void>가 동작을 완료할 때까지 기다린다.
  5. Future를 return한다.
  6. await 키워드가 붙어있으므로 Future가 완료될 때까지 기다린다. 4초뒤 완료되면 order에 'Large Latte'가 리턴된다.
  7. 'Your order is Large Latter'가 출력된다.
  8. printOrderMessage() 함수가 종료되었으므로 main에서 받았던 Future<void>도 completed가 되었다. 'where am I?' 가 출력되고 main함수가 종료된다.

출력 결과

Awaiting user order...
1
2
3
4
Your order is: Large Latte
where am I?

만약 여기에 printOrderMessage() 앞에 붙어있는 await 키워드를 뺀다면?

4번 printOrderMessage 함수 내에서 fetchUserOrder를 호출할 때 await 키워드가 붙어있어 바로 Future<void>를 리턴했으므로 main에서는 printOrderMessage의 함수가 끝났다고 느낄 것이고 바로 다음 실행문인 where am I? 를 출력할 것 이다.

Awaiting user order...
where am I?
1
2
3
4
Your order is: Large Latte

error handling

Future<void> printOrderMessage() async {
  try {
    var order = await fetchUserOrder();
    print('Awaiting user order...');
    print(order);
  } catch (err) {
    print('Caught error: $err');
  }
}

Future<String> fetchUserOrder() {
  // Imagine that this function is more complex.
  var str = Future.delayed(
      Duration(seconds: 4),
      () => throw 'Cannot locate user order');
  return str;
}

Future<void> main() async {
  await printOrderMessage();
}

그냥 똑같이 handling하면 된다.

FutureBuilder

Flutter에서 Future를 가지고 렌더링을 하고 싶을 때 사용하는 위젯

https://flutter.dev/docs/development/ui/widgets/async

https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

Widget that builds itself based on the latest snapshot of interaction with a Future.

A side-effect of this is that providing a new but already-completed future to a FutureBuilder will result in a single frame in the ConnectionState.waiting state. This is because there is no way to synchronously determine that a Future has already completed.

아래처럼 사용한다.

FutureBuilder(
  future: futureAlbum,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text(snapshot.data!.title);
    } else if (snapshot.hasError) {
      return Text("${snapshot.error}");
    }

    // By default, show a loading spinner.
    return CircularProgressIndicator();
  },
)

//https://flutter.dev/docs/cookbook/networking/fetch-data#5-display-the-data

reference

https://api.flutter.dev/flutter/dart-async/Future-class.html

https://velog.io/@jintak0401/FlutterDart-에서의-Future-asyncawait

https://dart.dev/codelabs/async-await

https://medium.com/flutter-community/a-guide-to-using-futures-in-flutter-for-beginners-ebeddfbfb967