본문 바로가기
Dart/기초

[Dart] Future, Stream, Async function

by 붕어사랑 티스토리 2021. 10. 15.
반응형

Async Function

async function은 비동기 함수이다. 말 그대로 함수의 작업이 끝나지 않아도 다음코드를 진행해 버린다. async function은 함수 리턴값으로 Future 클래스를 리턴한다. 이 Future클래스로 함수가 끝난 결과값을 처리할 수 있다.

 

아래 일반적은 sync function이 있다고 하자

String lookUpVersion() => '1.0.0';

이를 async로 바꾸면 아래처럼 함수에 async 키워드를 붙이고 Future클래스를 리턴해주면 된다

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

 

위 함수는 String값을 리턴하기에 Future<String> 이 되었다. 만약 값을 리턴하지 않으면 Future<void> 라고 적어주면 된다.

 

Future 클래스

비동기 함수는 Future 클래스를 리턴한다. 이 Future 클래스는 미래에 비동기 작업이 끝나면 값을 받아오는 클래스이다

Future클래스에는 콜백을 2개 등록할 수 있다.

 

  • 비동기 함수가 성공시 성공에대한 콜백
  • 비동기 함수가 error가 날 시 exception을 일으키는 콜백

 

Future는 두가지의 state를 가진다.

 

  • Uncompleted, 비동기 함수가 아직 끝나지 않은 상태
  • Completed, 비동기 함수가 작업이 끝난 상태. 성공하면 value를, 실패하면 error 값을 가져온다

Completing with a value

Future<T>는 비동기 처리가 성공시 T타입의 값을 가져온다. 만약 TString이면 Future<String> 으로 적어주면 된다

값을 리턴하지 않을경우 Future<void> 라고 적으면 된다.

 

Completing with an error

만약 비동기 처리가 fail되면 Future는 에러값을 가지게 된다.

 

 

 

아래의 예제는 2초후에 값을 리턴하는 코드의 예제이다.

 

Future<void> fetchUserOrder() {
  return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
}

void main() {
  fetchUserOrder();
  print('Fetching user order...');
}
Fetching user order...
//2초후
Large Latte

 

 

 

Future의 then(), catchError() 함수

then() 함수를 통해 Future가 complete 되면 실행해줄 콜백을 등록할 수 있다.

또한 catchError() 함수를 통해 에러처리를 해줄 수 있다.

 

then().catchError은 try-catch의 async버전이라고 생각하면 된다.

 

 

HttpRequest.getString(url).then((String result) {
  print(result);
}).catchError((e) {
  // Handle or ignore the error.
});

 

 

 

 

Async와 Await를 Future와 함께 사용하기

 

Await 키워드는 말 그대로 비동기 작업을 기다리는 키워드이다

 

두가지만 기억하자

 

  • async function을 정의하려면, 함수바디 앞에 async를 붙여준다
  • await는 async 함수 안에서만 동작한다!

아래 예제를 보자

 

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('Fetching user order...');
  print(createOrderMessage());
}
Fetching user order...
Your order is: Instance of '_Future<String>'

 

위 예제는 async와 await를 적용하지 않은 예제이다. 특이하게도 createOrderMessage()함수는 String을 리턴하는데 리턴값이 _Future<String>이 된 걸 알 수 있다.

아마 order값이 Future이고 String과 결합되어 Future로 바뀐걸로 추측된다.(구글놈들이 이런것좀 써달라고...)

 

 

자 이제 async와 await를 달아주자.

 

먼저 결과물에서 알 수 있듯이 main함수에서도 Future를 기다려주어야 한다. 고로 await를 달자

print(await createOrderMessage());

아까 주의사항이 기억나는가? await는 async안에서만 놀아야된다. 고로 main함수에도 async를 달아주자

Future<void> main() async { ··· }

 

 

 

그럼 아래와 같은 코드가 작성된다.

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...
Your order is: Large Latte

 

 

 

 

여러개의 Future waiting하기

Future.wait() 함수를 이용하면 여러개의 Future를 waiting 할 수 있다.

 

Future<void> deleteLotsOfFiles() async =>  ...
Future<void> copyLotsOfFiles() async =>  ...
Future<void> checksumLotsOfOtherFiles() async =>  ...

await Future.wait([
  deleteLotsOfFiles(),
  copyLotsOfFiles(),
  checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');

 

 

 

Future channing 하기

then() 메소드는 Future 클래스를 리턴한다. 이를 통해 연속해서 then() 함수로 콜백을 등록하여 여러개의 작업을 순서대로 실행시킬 수 있다.

Future result = costlyQuery(url);
result
    .then((value) => expensiveWork(value))
    .then((_) => lengthyComputation())
    .then((_) => print('Done!'))
    .catchError((exception) {
  /* Handle exception... */
});

위 코드는 아래와 같다

 

try {
  final value = await costlyQuery(url);
  await expensiveWork(value);
  await lengthyComputation();
  print('Done!');
} catch (e) {
  /* Handle exception... */
}

 

Stream 클래스

데이터의 흐름을 비동기적으로 받아온다. 가령 예를들어 스마트폰 액정을 터치하거나 컴퓨터의 화면에서 마우스를 클릭할때 정보를 받아와 처리하는 클래스이다. Stream.listen 에 콜백을 등록하여 스트림 데이터를 처리할 수 있다.

 

 

 

 

await for 로 Stream 데이터 다루기

 

Stream 데이터를 다루는 방법은 두가지가 있다

 

  • async와 await for을 이용하여 Stream 데이터 처리
  • Stream API를 이용하는 방법, listen()

 

지금은 await for을 이용하여 스트림 데이터를 다루는 방법을 설명하겠다

 

 

사용법은 아래와 같다

 

await for (varOrType identifier in expression) {
  // Executes each time the stream emits a value.
}

 

 

주의사항 2가지만 기억하자

  • expression의 데이터타입은 반드시 Stream 이여야 한다!!
  • await for은 async 함수 안에서 실행되어야 한다!! (await는 async 안에서만 놀아야함)

 

await for은 아래와 같은 순서로 작동한다.

  1. Stream으로부터 데이터를 받을때 까지 기다린다.
  2. Stream으로부터 데이저를 전달받으면 전달받은 데이터로 for loop를 실행한다.
  3. 1번과 2번을 Stream이 close 될 때 까지 반복한다

 

 

아래는 request 스트림을 처리하는 예제이다. 만약 await for을 main함수에 쓰고싶으면 main 함수에 async를 붙여준다

Future<void> main() async {
  // ...
  await for (final request in requestServer) {
    handleRequest(request);
  }
  // ...
}

 

 

listen()을 통한 Stream 데이터 다루기

 

Stream 데이터는 listen() 함수에 콜백을 달아 처리도 가능하다.

 

아래 예제는 버튼클릭에 대한 핸들러를 listen()에 등록해주는 예제이다.

 

// Add an event handler to a button.
submitButton.onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

 

 

 

 

 

Stream에서 일부 이벤트만 다루기

Stream 클래스는 first, last, single이라는 프로퍼티를 가지고 있다. 이 프로퍼티로 이벤트 하나만 처리 할 수 있다.

single 프로퍼티는 Stream이 보내주는 데이터가 딱 하나일때 사용한다. 1개 이상이면 에러를 일으킨다.

 

 

만약 Stream 이벤트를 handling 하기 전에 test해보고 싶다면 firstWhere(), lastWhere(), singleWhere() 함수를 사용한다.

 

  • firstWhere() : 테스트를 만족하는 요소중 첫번째 요소만 리턴
  • lastWhere() : 테스트를 만족하는 요소중 마지막 요소 리턴
  • singleWhere() : 테스트를 만족하는 요소가 하나면 리턴, 2개이상일경우 stateError를 일으킴

 

 

또한 skip(), skipWhile(), take(), takeWhile()로 일부 이벤트를 스킵하거나 일부 부분만 처리할 수 있다.

 

 

 

Stream 데이터 타입변형

 

transform()을 이용하여 데이터의 타입을 변경할 수 있다.

 

var lines = inputStream
    .transform(utf8.decoder)
    .transform(LineSplitter());

 

 

 

 

Stream의 에러 처리

 

await for loop는 try and catch 구문으로 예외처리 한다

Future<void> readFileAwaitFor() async {
  var config = File('config.txt');
  Stream<List<int>> inputStream = config.openRead();

  var lines = inputStream
      .transform(utf8.decoder)
      .transform(LineSplitter());
  try {
    await for (final line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}

 

listen() API를 이용한다면 성공했을때는 onDone에 콜백을, 에러가 다면 onError에 에러처리 콜백을 달아준다

 

var config = File('config.txt');
Stream<List<int>> inputStream = config.openRead();

inputStream
    .transform(utf8.decoder)
    .transform(LineSplitter())
    .listen((String line) {
  print('Got ${line.length} characters from stream');
}, onDone: () {
  print('file is now closed');
}, onError: (e) {
  print(e);
});

 

반응형

댓글