[Flutter] 비동기를 위한 Future
Future?
Future의 정의는 공식 문서에서 다음과 같이 소개하고 있다.
A future (lower case “f”) is an instance of the Future (capitalized “F”) class. A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.
미래는 Future 클래스의 인스턴스라고 한다.
또한, 미래는 비동기 작업의 결과를 나타내고 완료되지 않은 상태 또는 완료된 상태의 두 가지 상태를 가진다고 한다.
여기서 완료되지 않은 상태와 완료된 상태에 대해서도 추가적인 설명을 하고 있다.
완료되지 않은 상태 (Uncompleted)
: 비동기 함수를 호출하면 완료되지 않은 미래가 반한되기에 미래는 함수의 비동기 작업이 완료되거나 오류가 발생하기를 기다린다.
완료된 상태 (Completed)
: 비동기 작업이 성공하면 미래는 값으로 완료되고, 그렇지 않으면 오류와 함께 완료된다.
예시를 한번 살펴보자
Future 예시 코드 1
/* Future - 미래
* 미래에 받아올 값
* */
Future<String> name = Future.value('홍길동');
Future<int> number = Future.value(1);
Future<bool> isTrue = Future.value(true);
print('함수 시작');
/* 2개의 파라미터
* 1번 파라미터 - 지연할 기간 (얼마나 지연할 것인지) Duration
* 2번 파라미터 - 지연 시간이 지난 후 실행할 함수
* */
Future.delayed(Duration(seconds: 2), () {
print('Delay 끝');
});
Future 키워드가 async를 가지게 되는데,
이 때 delayed() 라는 함수를 사용하게 된다.
지연 시간과 지연 시간 이후의 실행 함수를 작성하게 된다.
Future 예시 코드 2 (delayed)
void main() {
/* Future - 미래
* 미래에 받아올 값
* */
Future<String> name = Future.value('홍길동');
Future<int> number = Future.value(1);
Future<bool> isTrue = Future.value(true);
addNumbers(1, 1);
addNumbers(2, 2);
}
void addNumbers(int num1, int num2) {
print('계산 시작 : $num + $num2');
// 시뮬레이션
Future.delayed(Duration(seconds: 2), () {
print('계산 완료 : $num1 + $num2 = ${num1 + num2}');
});
print('함수 완료');
}
/* 결과
* 계산 시작 : 1 + 1
* 함수 완료
* 계산 시작 : 1 + 2
* 함수 완료
* 계산 완료 : 1 + 1 = 2
* 계산 완료 : 2 + 2 = 4
* */
여기서 delayed() 함수를 사용한 비동기는 addNumbers() 함수 2개가 실행된 이후에,
delayed 된 시간 이후에 계산 완료 문구를 반환하는 것을 확인할 수 있다.
해당 방식과 비슷하지만 다르게, 계산을 동시에 시작한 이후에
모든 함수 실행을 비동기적으로 반환할 수 있도록 하려면
async-await을 사용해주면 된다.
Future 예시 코드 3 (async-await)
void main() {
/* Future - 미래
* 미래에 받아올 값
* */
Future<String> name = Future.value('홍길동');
Future<int> number = Future.value(1);
Future<bool> isTrue = Future.value(true);
addNumbers(1, 1);
addNumbers(2, 2);
}
void addNumbers(int num1, int num2) async {
print('계산 시작 : $num1 + $num2');
// 시뮬레이션
await Future.delayed(Duration(seconds: 2), () {
print('계산 완료 : $num1 + $num2 = ${num1 + num2}');
});
print('함수 완료');
}
/* 결과
* 계산 시작 : 1 + 1
* 계산 시작 : 2 + 2
* 계산 완료 : 1 + 1 = 2
* 함수 완료
* 계산 완료 : 2 + 2 = 4
* 함수 완료
* */
async 키워드는 파라미터 이후에 함수 로직 이전 사이에 넣어주면 되고,
비동기적으로 지연될 로직 외부에 await 키워드를 달아주면 된다.
Future<void> main() async { ··· }
추가적으로 함수에 선언된 반환 타입(유형)이 있는 경우에는 제너릭을 작성하면 된다.
함수가 명시적으로 값을 반환하지 않는 경우에는 Future<void>를 작성하면 된다.
이렇게 처리되는 비동기 작업에서는 에러 처리를 진행할 수 있는데,
공식 문서에서 확인해보면 try-catch를 통해 예시를 제공해주고 있다.
Future 예시 코드 4 (예외처리)
Future<String> changeUsername() async {
try {
return await fetchNewUsername();
} catch (err) {
return err.toString();
}
}
비동기적으로 fetchNewUsername() 함수를 처리하고,
에러를 발견하면 toString() 메서드를 사용하여 예외와 오류 모두를 문자로 바꿀 수 있다.
앞서서 소개한 Future와 붙어다니는 개념 중 하나인 Stream이 존재하는데,
Stream은 데이터나 이벤트가 들어오는 통로를 의미한다.
어떠한 순간에 데이터가 들어올지 확실히 알기 어렵기 때문에 미래에 값을 받는 Future와 함께
비동기 작업을 할 때 주로 사용기에 비슷하다고 생각할 수 있다.
Stream과 Future의 차이점은 Future는 즉시 완료되지 않는 계산을 나타내어
반환과 동시에 결과가 포함되어 Future에 알려주지만,
Stream은 구독자패턴 혹은 관찰자패턴으로 구독자(listen)가 관찰 대상(stream)을
구독하여 관찰 대상에 변화가 발생하면 구독자에게 해당 변화를 알려준다는 차이점이 존재한다.
이러한 Stream에 관련해서 자세한 것은 다음 포스팅에...💫