[TIL-010] Dart 실전 문법 파헤치기 - Future Class

비동기 프로그래밍(Asynchronous Programming)

  • 작업이 완료될 때까지 기다리지 않고, 미래의 특정 시점에 값을 반환한다.
  • 결과값이 나올 때까지 멈춰 있지 않고, 수행할 수 있는 다른 모든 작업을 찾아서 수행한다.
  • 다른 모든 작업이 끝나야만 수행한다.
  • Dart에는 `Future`, `Stream` 클래스를 사용한다.

 

Future Class

Future<int> number = Future.value(1);
Future<String> name = Future.value('mark');
Future<bool> isOddNumber = Future.value(1.isOdd);
  • `Future`를 사용하려면 타입을 정의할 때 `Future<T>`로 정의하면 된다.
  • 하나의 작업에 대해 값이나 이벤트가 한 번 발생하는 단일 비동기 작업에 사용한다.

 

Future.delayed()

void introduce(String name) {
  print('$name의 자기소개 시작 !');

  Future.delayed(Duration(microseconds: 1), () {
    print('안녕 ? 나는 0.000001초 지난 $name ~');
  });

  print('$name의 자기소개 끝 !');
}

void main() {
  introduce('Lee');
  introduce('Kim');
}

/*
Lee의 자기소개 시작 !
Lee의 자기소개 끝 !
Kim의 자기소개 시작 !
Kim의 자기소개 끝 !

-- 🚨 0.000001초가 지난 상황에서 모든 코드 실행이 다 끝났으면 Future 실행 --

안녕 ? 나는 0.000001초 지난 Lee
안녕 ? 나는 0.000001초 지난 Kim
*/
  • 호출 순서
    1. `introduce('Lee');` 실행
    2. `print('Lee의 자기소개 시작 !');` 실행
    3. `Future.delayed(Duration(microseconds: 1), () {
          print('안녕 ? 나는 0.000001초 지난 Lee~');
        });` 는 0.000001초 기다림
    4. 0.000001초를 기다리는 중에 나머지 코드를 실행
    5. `print('Lee의 자기소개 끝 !');` 실행
    6. 분명 0.000001초는 지났지만 3번은 동작하지 않고 동작 할 수 있는 다른 코드를 찾음
    7. `introduce('Kim');` 실행
    8. `print('Kim의 자기소개 시작 !');` 실행
    9. `Future.delayed(Duration( micro seconds: 1), () {
          print('안녕 ? 나는 0.000001초 지난 Kim~');
        });` 는 0.000001초 기다림
    10. 0.000001초를 기다리는 중에 나머지 코드를 실행
    11. `print('Kim의 자기소개 끝 !');` 실행
    12. 모든 코드들이 다 실행 됐으므로 기다렸던 `Future`문이 순서대로 실행
    13. `Future.delayed(Duration(microseconds: 1), () {
          print('안녕 ? 나는 0.000001초 지난 Lee~');
       });` 실행
    14. `Future.delayed(Duration(microseconds: 1), () {
          print('안녕 ? 나는 0.000001초 지난 Kim~');
       });` 실행

 

async - await

기본 동작

async함수 내에서 비동기를 동기적으로 실행되도록 하는 키워드. 

비동기를 수행하는 클래스에는 `await`키워드를, 비동기 코드를 랩핑하고 있는 함수에는 `async`키워드를 사용한다.

void introduce(String name) async {
  print('$name의 자기소개 시작 !');

  await Future.delayed(Duration(seconds: 2), () {
    print('안녕 ? 나는 $name ~');
  });

  print('$name의 자기소개 끝 !');
}

void main() {
  introduce('Lee');
}

/*
Lee의 자기소개 시작 !
안녕 ? 나는 2초 후의 Lee ~
Lee의 자기소개 끝 !
*/
  • 호출 순서
    1. ` introduce('Lee');` 실행
    2. ` print('Lee의 자기소개 시작 !');` 실행
    3. `await Future.delayed(Duration(seconds: 2), () {
          print('안녕 ? 나는 Lee ~');
       `}); 는 2초 기다림
    4. 2초 후에 print('안녕 ? 나는 Lee ~'); 실행
    5. `print('Lee의 자기소개 끝 !');` 실행
  • `async` 함수 내에서 `await` 키워드를 만나면, 해당 지점부터 비동기 작업(Future)이 완료될 때까지 기다린 후에 다음 코드를 실행한다.

 

심화 동작1

void introduce(String name) async {
  print('$name의 자기소개 시작 !');
  
  await Future.delayed(Duration(seconds: 2), () {
    print('안녕 ? 나는 2초도 지났고 모든 코드의 동작이 끝난 후의 $name ~');
  });
	
  print('await가 끝났으니 $name의 자기소개 끝 !');
}

void main() {
  introduce('Lee');
  introduce('Kim');
}

/*
Lee의 자기소개 시작 !
Kim의 자기소개 시작 !

-- 🚨 2초가 지난 상황에서 모든 코드 실행이 다 끝났으면 Future 실행 --

안녕 ? 나는 2초도 지났고 모든 코드의 동작이 끝난 후의 Lee ~
await가 끝났으니 Lee의 자기소개 끝 !
안녕 ? 나는 2초도 지났고 모든 코드의 동작이 끝난 후의 Kim ~
await가 끝났으니 Kim의 자기소개 끝 !
*/
  • 호출 순서
    1. `introduce('Lee');`실행
    2. `print('Lee의 자기소개 시작 !');`실행
    3. `await Future.delayed(Duration(seconds: 2), () {
          print('안녕 ? 나는 2초도 지났고 모든 코드의 동작이 끝난 후의 Lee ~');
       });`는 `await` 키워드로 인해 비동기 작업(Future)이 완료될 때까지 기다린다.
    4. 그래서 `print('await가 끝났으니 Lee의 자기소개 끝 !');`도 기다린다.
    5. 비동기가 동기처럼 동작하기 위해서는 `async`함수 내에서만 가능하다. `introduce('Lee');`내에서 비동기 작업(Future)을 기다리는 동안 동작할 다른 코드가 없으니 밖으로 나가 다른 코드들을 살핀다.
    6. 실행할 코드가 남아있으므로introduce('Kim');를 실행 
    7. `print('Kim의 자기소개 시작 !');`실행
    8. `await Future.delayed(Duration(seconds: 2), () {
          print('안녕 ? 나는 2초도 지났고 모든 코드의 동작이 끝난 후의 Kim ~');
       });`는 `await` 키워드로 인해 비동기 작업(Future)이 완료될 때까지 기다린다.
    9. 마찬가지로 `print('await가 끝났으니 Kim의 자기소개 끝 !');`도 기다린다.
    10. `introduce('Kim');`내에서 비동기 작업(Future)을 기다리는 동안 동작할 다른 코드가 없으니 밖으로 나가 다른 코드들을 살핀다.
    11. 동작할 수 있는 다른 코드가 없으니 비동기 작업(Future)을 기다렸다가 완료가 되면 아까 진행되지 못했던 코드들이 다시 진행된다.
    12. `await Future.delayed(Duration(seconds: 2), () {
          print('안녕 ? 나는 2초도 지났고 모든 코드의 동작이 끝난 후의 Lee ~');
       });`실행
    13. `print('await가 끝났으니 Lee의 자기소개 끝 !');`실행
    14. `print('await가 끝났으니 Kim의 자기소개 끝 !');`실행

 

심화 동작2

심화 동작1에서 5번, 6번 순서가 가능했던 이유는

  • `await`키워드는 `async`함수내에서만 동기적으로 동작할 뿐, 그 외 코드에는 비동기로 동작한다는 특징과
  • `aysnc`키워드를 사용한 함수도 암시적으로 Future가 되는 특징 때문이다.

 

그렇다면 심화 동작1도 동기적으로 동작시켜 보자.

Future<void> introduce(String name) async {
  print('$name의 자기소개 시작 !');
  
  await Future.delayed(Duration(seconds: 2), () {
    print('안녕 ? 나는 2초도 지났고 모든 코드의 동작이 끝난 후의 $name ~');
  });
	
  print('await가 끝났으니 $name의 자기소개 끝 !');
}

void main() async {
  await introduce('Lee');
  await introduce('Kim');
}

/*
Lee의 자기소개 시작 !
안녕 ? 나는 2초도 지났고 모든 코드의 동작이 끝난 후의 Lee ~
await가 끝났으니 Lee의 자기소개 끝 !
Kim의 자기소개 시작 !
안녕 ? 나는 2초도 지났고 모든 코드의 동작이 끝난 후의 Kim ~
await가 끝났으니 Kim의 자기소개 끝 !
*/
  • `aysnc`키워드를 사용한 함수도 암시적으로 `Future`가 된다 했으니 `await`키워드를 붙여 호출할 수 있다.
  • 암시적인 `Future`이므로 명시적으로 변경하기 위해 `introduce async`함수의 타입을 void는 그대로 가져가고 Future<T> 타입을 이용하여 `Future<void>`로 변경한다.
  • 후에 `await`키워드를 사용해 `introduce`함수를 실행한다.
  • 이 때, `await`키워드는 `async`함수 내에서만 사용가능하므로 `main`함수에 `async`키워드를 사용하면 된다.

 

비동기 함수 만들기

심화 동작2에서 배운 '`aysnc`키워드를 사용한 함수는 암시적으로 Future가 되는 특징'을 이용해 일반 함수를 비동기 함수로 만들 수 있다.

Future<String> fetchData() async {
  return '데이터 로드';
}

void main() {
  fetchData().then((data) {
    print(data);
  });

  print('비동기 작업 시작');
}
  • 일반 함수인 `fetchData`에 `async`키워드를 사용해 `Future`로 만든다.
  • `async`키워드만 있으면 반환 타입을 입력하지 않아도 타입 추론으로 `Future<dynamic>`이 반환 타입이 되기 때문에 비동기 함수가 된다. 그러나 명시적으로 입력하는 것이 좋다.
  • `Future`가 되었으니 반환 타입도 `Future<T>`형태가 된다.
  • 반환을 `String`타입으로 하고 있으므로 `fetchData`함수의 반환 타입은 `Future<String>`이 된다.

 

마무리

모든 프로그래밍 언어에서 비동기 작업은 조금 난이도가 있는 편인 것 같다.

그렇지만 이것을 알아야 서버와 통신하는 멋진 앱을 만들 수 있으므로 어려워도 가치가 있지 않을까?

Dart 언어 문법을 다 알고난 후에 빨리 Flutter로 앱을 개발하고 싶다.