Dart언어의 특징 중에 하나는 동기식 / 비동기식 프로그래밍을 동시에 지원하는 언어라는 점입니다.

그렇다면 어떻게 두 가지 방식이 공존 할 수 있을까요? 바로, Flutter의 async 명령어를 활용하면 가능합니다.

async에는 두 종류가 있는데, Future 형태의 async / Stream 형태의 async* 가 있습니다.

이번 페이지에서는 Future 형태의 async를 알아보고자 합니다. (Stream은 아마도 다른 고수분께서 써주시겠지...)

Async Future

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

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

// Fetching user order...
// Large Latte

간략하게 코드를 읽는 다면, fetchUserOrder 함수 내의 print('Large Latte') 가 먼저 실행 될 것 같지만, 실행해보면 아시겠지만 print('Fetching user order...') 함수가 먼저 실행 되고 나서 잠시 후 실행이 됩니다.

이유는 생각보다 간단합니다. Future.delayed 함수를 통해 실행이 2초 뒤로 밀렸기 때문입니다.

이처럼 Future 객체를 활용하여 실행 시점을 늦추거나 하는 등의 작업을 할 수 있습니다.

거꾸로 이런 작업도 가능하겠죠?

bool isDecision = false;

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

Future<void> fetchUserOrder() {
  return Future.delayed(const Duration(seconds: 2), () {
      if (!isDecision) {
        print('User has not decided on the menu yet.');
        return fetchUserOrder();
      }
      print('Large Latte');
    } 
  );
}

Future<void> userDecision() {
  return Future.delayed(const Duration(seconds: 10), () => isDecision = true);
}

// Fetching user order...
// User has not decided on the menu yet.
// User has not decided on the menu yet.
// User has not decided on the menu yet.
// User has not decided on the menu yet.
// Large Latte

위 코드에서는 두 가지의 Future 를 반환하는 함수를 활용함으로, 특정 변수의 상태를 확인하여 코드의 진행이나 결과값을 개발자의 의도대로 수정 할 수도 있습니다.

또한 Future를 활용한 코딩 방식으로는 이런 방법도 있습니다.

String? menu;

void main() {
  fetchUserOrder();
  userDecision().then((decision) => menu = decision);
  print('Fetching user order...');
}

Future<void> fetchUserOrder() {
  return Future.delayed(const Duration(seconds: 2), () {
      if (menu == null) {
        print('User has not decided on the menu yet.');
        return fetchUserOrder();
      }
      print(menu);
    } 
  );
}

Future<String> userDecision() {
  return Future.delayed(const Duration(seconds: 10), () => 'Large Latte');
}

// Fetching user order...
// User has not decided on the menu yet.
// User has not decided on the menu yet.
// User has not decided on the menu yet.
// User has not decided on the menu yet.
// Large Latte

위의 코드와 결과는 다르지 않지만, Future의 제네릭 값이 void에서 String으로 바뀌었고, 그 값이 특정 시점에 대입 된 뒤, 그 값을 출력하는 식으로 변경이 되었습니다.

이 과정에서 Future의 내장 함수인 then 이라는 함수가 사용 되었는데, 이 함수는 Future를 반환하는 함수가 실행이 종료 되고, 해당 Future의 결과에 따른 값을 매개변수로 전달 받아 코드에 활용 할 수 있습니다.

그리고 Flutter에서는 Future를 조금 더 쓰기 쉽게 하기 위해 FutureBuilder 라는 객체를 제공하고 있습니다.

FutureBuilder

https://dartpad.dev/?id=84866125fe0e3f51e265d5c5acc51aa9&null_safety=true

위의 본문 코드를 보면, FutureBuilder를 활용하여 특정 future의 결과가 나오기 전 / 후의 상태를 나누어 각기 다른 Widget을 보여주는 것도 가능합니다. builder의 snapshot 객체에 데이터가 들어와 있는지, 아닌지 알 수 있습니다.