조용한 담장

Dart : Asynchronous programming 본문

Dart

Dart : Asynchronous programming

iosroid 2019. 10. 24. 17:24

Dart : Asynchronous programming

Dart 의 비동기 프로그래밍.

Asynchrony support

비동기 프로그래밍은 Future or Stream 을 통해 지원한다.

아래 코드를 보면서 감을 잡아보자.

  1. main() 에서 첫번째 print() 를 출력하고 두번째 print() 에서 출력할 내용을 await createOrderMessage() 이 리턴할 때 까지 기다린다.

  2. createOrderMessage() 에서 var order 변수를 생성하고 값을 설정하기 위해 await getUserOrder() 의 리턴값을 기다린다.

  3. getUserOrder() 에서 Future.delayed() 가 4초 후 'Large Latte' 문자열을 리턴한다.

  4. await getUserOrder() 가 문자열을 리턴하면 createOrderMessage() 의 나머지 코드가 수행되어 'Your order is: $order' 문자열을 리턴한다.

  5. createOrderMessage() 이 리턴되어 기다리던 print() 동작이 수행되어 인자로 전달된 'Your order is: Large Latte' 문자열을 출력한다.

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

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

// Asynchronous
main() async {
  print('Fetching user order...');
  print(await createOrderMessage());
}

// 'Fetching user order...'
// 'Your order is: Large Latte'

Future class

An object representing a delayed computation.

A Future is used to represent a potential value, or error, that will be available at some time in the future.

Future 는 특정 코드가 비동기적으로 수행된 후 의 결과가 저장되면 콜백으로 알려주고 저장된 데이터를 읽을 수 있게 된다.

future 의 state 는 Uncompleted 와 Completed 로 나눠진다.

  • Uncompleted : 비동기로 수행되고 있는 함수, 코드가 완료되지 않아 결과나 error 를 기다리고 있는 상태이다.
  • Completed
    • Completing with a value : 비동기 함수, 코드 수행이 완료되어 정상적인 결과 값으로 Future 나 Future 를 가지고 있다.
    • Completing with an error : 비동기 함수, 코드 수행에 에러가 발생하여 에러값을 가지고 있다.

If a future doesn’t produce a usable value, then the future’s type is Future.

Future<void> 는 리턴값이 없는 void 함수를 수행할때 가지게 되는 Future 타입이다.

아래처럼 Future 의 데이터를 읽어야 하는 receiver 는 콜백 함수를 등록해서 결과값을 처리 한다.

Future<int> future = getFuture();
future.then((value) => handleValue(value))
      .catchError((error) => handleError(error));

위의 콜백함수와 그 결과 처리 코드를 좀 더 간결하게 쓸 수 있도록 async, await 를 제공한다.

async, await

async 로 표시된 함수는 내부 코드에서 await 키워드를 만나면 그 뒤의 동작을 비동기적으로 처리하고 결과를 기다리며 진행을 멈추고 Future 가 리턴되면 다시 동작을 수행한다.

async function

An async function is a function whose body is marked with the async modifier. Adding the async keyword to a function makes it return a Future.

함수명 뒤에 async 키워드를 가진 함수로 비동기적인 속성을 가지게 되고 그 결과로 Future 를 리턴한다.

Future<String> lookUpVersion() async => '1.0.0';

 

 

awaitasync function 내에서만 사용해야 한다.

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

위 코드 동작을 보면, Future 를 리턴하는 async function 인 checkVersion()await 를 만나면 자신의 동작은 멈추고 비동기 적으로 lookUpVersion() 이 수행되며 그 결과(Future)가 리턴되면 var version 에 값을 저장하고 나머지 코드를 수행한다.

 

await 는 error 도 전달하는 데 try, catch, and finally 를 통해 처리한다.

try {
  version = await lookUpVersion();
} catch (e) {
  // React to inability to look up the version
}

 

async function 안에서 await 는 여러번 사용될 수 있다.

// wait three times.
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

 

Future class 의 메소드들을 then() 을 사용하여 구현하는 코드 보다 await/async 를 사용한 코드가 조금 더 간단하고 이해하기 좋은 장점이 있다.

PREFER async/await over using raw futures.

 

// async/await
Future<int> countActivePlayers(String teamName) async {
  try {
    var team = await downloadTeam(teamName);
    if (team == null) return 0;

    var players = await team.roster;
    return players.where((player) => player.isActive).length;
  } catch (e) {
    log.error(e);
    return 0;
  }
}

// Future methods
Future<int> countActivePlayers(String teamName) {
  return downloadTeam(teamName).then((team) {
    if (team == null) return Future.value(0);

    return team.roster.then((players) {
      return players.where((player) => player.isActive).length;
    });
  }).catchError((e) {
    log.error(e);
    return 0;
  });
}

async 를 쓰지 않아도 되는 경우엔 생략하자.

Future afterTwoThings(Future first, Future second) {
  return Future.wait([first, second]);
}

// bad
Future afterTwoThings(Future first, Future second) async {
  return Future.wait([first, second]);
}

위 예제의 afterTwoThings()async 없이도 동작이 동일하다.

 

사용해야 하는 예제로 아래의 세가지 경우를 참고한다.

 

// 1. await 를 사용하는 경우
Future usesAwait(Future later) async {
  print(await later);
}

// 2. 비동기적으로 error 를 리턴하는 경우.
//    Future.error() 메소드 보다 코드가 간단해 진다.
Future asyncError() async {
  throw 'Error!';
}

// 3. Future 를 통해 값을 전달하는 경우.
//    Future.value() 메소드 보다 코드가 간단해 진다.
Future asyncValue() async => 'value';

Future Examples

Future class 의 동작을 확인해본 간단한 snippet.

Future()

 

void future_constructor() {
final future = Future(() => 'Future() done');
future.then((value) {
print('$value');
})
..whenComplete(() {
print('Future() completed');
});
}
view raw future1.dart hosted with ❤ by GitHub

 

Future.value()

지정한 결과값을 가진 future 를 생성하는 constructor

 

void future_constructor_value() {
Future.value('Future.value() done')
..then((value) {
print('$value');
});
}
view raw future2.dart hosted with ❤ by GitHub

 

Future.delayed()

특정 시간 후에 동작을 수행하는 future 를 생성하는 constructor

timeout() 메소드를 이용해 일부러 에러를 발생하는 예제 포함.

 

void future_constructor_delayed() {
Future.delayed(
Duration(seconds: 3),
() => 'Future.delayed(3 secs) done')
.then((value) { print('$value'); })
.whenComplete(
() { print('Future.delayed(3 secs) completed'); });
Future.delayed(
Duration(seconds: 10),
() => 'Future.delayed(10 secs) done')
.timeout(Duration(seconds: 5))
.whenComplete(
() { print('Future.delayed(10 secs) completed'); })
.then((value) { print('$value'); })
.catchError((error) { print('error $error'); });
}
// output:
// Future.delayed(3 secs) done
// Future.delayed(3 secs) completed
// Future.delayed(10 secs) completed
// error TimeoutException after 0:00:05.000000: Future not completed
view raw future3.dart hosted with ❤ by GitHub

 

Future.forEach(Iterable elements, FutureOr action(T element))

elements 각각의 값을 가지고 순서대로 action 을 수행하여 최종 결과를 future 로 만드는 static method.

모든 action 이 정상 수행 되면 null 을 가지는 future 를 리턴하는 특성 때문에 결과값을 전달하는 용도로 사용할 수는 없다.

Future.wait() 는 값을 전달할 수 있다.

아래 코드에서 elements 의 순서대로 수행되나 결과의 순서는 각각의 비동기 수행의 결과에 따라 다를 것이다.

 

void future_forEach(List<String> urls) {
Future.forEach(urls, (url) {
print('url:$url');
http.get(url).then((response) {
print('statusCode:${response.statusCode}');
});
}).catchError((error) { print('$error'); });
}
// output
// url:https://www.google.com
// url:https://duckduckgo.com
// statusCode:200
// statusCode:200
view raw future4.dart hosted with ❤ by GitHub

 

Future.wait(IterableFuture futures, { bool eagerError: false, void cleanUp(T successValue) })

Future.error('Future.error()'), 를 주석처리 하면 두번째 output 결과를 얻는다.

 

void future_wait(String url1, String url2) {
Future.wait([
http.get(url1),
http.get(url2),
Future.error('Future.error()'),
],
cleanUp: (value) { print('CleanUp() : ${value?.statusCode}'); }
).then((value) {
for (var f in value) {
print('statusCode: ${f?.statusCode}');
}
}).catchError((error) { print('error : $error'); });
}
// output
// CleanUp() : 200
// CleanUp() : 200
// error : Future.error()
// ouput
// statusCode: 200
// statusCode: 200
view raw future5.dart hosted with ❤ by GitHub

 

Future.doWhile(FutureOr action())

false 를 리턴할때 까지 action 을 반복 수행한다.

 

void future_dowhile() {
final nums = [1, 2, 3, 4, 5];
var sum = 0;
var count = 0;
Future.doWhile(() {
print('count:$count');
if (count == nums.length) {
return false;
} else {
sum += nums[count];
count++;
return true;
}
});
print('sum:$sum');
}
// output
// count:0
// count:1
// count:2
// count:3
// count:4
// count:5
// sum:15
view raw future6.dart hosted with ❤ by GitHub

 

Reference

Language tour

Asynchronous programming: futures,async,await

Asynchronous programming: streams

dart:async library

Effective Dart

'Dart' 카테고리의 다른 글

Dart : Collections  (0) 2019.11.19
Dart : print  (0) 2019.11.04
Dart : Class  (0) 2019.10.02
Dart : Exceptions  (0) 2019.09.20
Dart : Control flow statements  (0) 2019.09.17