# Future와 비동기 함수

Dart는 싱글 스레드 기반의 언어이지만, 비동기 프로그래밍을 통해 여러 작업을 효율적으로 처리할 수 있다. 비동기 작업을 처리하는 기본 도구는 `Future`이다. 이 장에서는 `Future`와 비동기 함수의 동작 원리와 사용법에 대해 깊이 있는 설명을 제공하겠다.

#### Future의 개념

`Future`는 미래에 완료될 작업을 나타내는 객체이다. 작업이 완료되면 해당 결과를 반환하거나, 오류가 발생하면 예외를 던진다. `Future`는 주로 시간이 걸리는 작업(예: 파일 읽기, 네트워크 요청)에서 사용된다.

다음은 `Future`의 상태 변화를 도식으로 표현한 것이다:

{% @mermaid/diagram content="graph LR
A\[Future] --> B\[Pending]
B --> C\[Completed]
C -->|Success| D\[Value]
C -->|Error| E\[Exception]" %}

1. **Pending 상태**: `Future`가 생성되고 작업이 아직 완료되지 않은 상태.
2. **Completed 상태**: 작업이 완료되었으며, 성공적으로 값이 반환되거나 오류가 발생함.
3. **Value 또는 Exception**: 작업이 성공한 경우 값을 반환하고, 실패한 경우 예외를 발생시킴.

#### 비동기 함수의 정의와 호출

Dart에서 비동기 함수는 `async` 키워드를 사용하여 정의한다. 비동기 함수는 일반적으로 `Future` 객체를 반환하며, 이 함수 내부에서 `await`를 통해 비동기 작업이 완료될 때까지 기다릴 수 있다. 비동기 함수의 핵심 개념은 바로 동기적 코드 흐름과 비동기적 작업을 결합하여 복잡한 비동기 로직을 보다 쉽게 구현할 수 있다는 것이다.

```dart
Future<int> fetchData() async {
  return 42;
}
```

위 예제는 간단한 비동기 함수 `fetchData`를 정의한 것이다. 이 함수는 `42`라는 값을 담은 `Future`를 반환한다.

#### Future의 사용 방식

`Future`를 사용하여 비동기 작업을 처리할 때는 두 가지 방식이 있다.

1. **then() 사용**: `then()` 메서드는 `Future`가 완료된 후 호출된다. 성공적으로 완료된 경우 값을 전달받을 수 있으며, 오류가 발생한 경우 `catchError`를 사용하여 예외를 처리할 수 있다.

```dart
fetchData().then((value) {
  print("Data: $value");
}).catchError((error) {
  print("Error: $error");
});
```

2. **await 사용**: `async` 함수 내에서 `await` 키워드를 사용하면, `Future`가 완료될 때까지 기다리고, 완료된 값을 직접 변수에 할당할 수 있다. `await`를 사용하면 코드가 마치 동기적으로 실행되는 것처럼 보이지만, 실제로는 비동기 작업이 진행되고 있는 것이다.

```dart
void fetchAndPrintData() async {
  int data = await fetchData();
  print("Data: $data");
}
```

#### Future의 결합

Dart에서는 여러 `Future`를 결합하여 동시에 실행하거나, 순차적으로 실행할 수 있다.

1. **Future.wait()**: 여러 `Future`가 모두 완료될 때까지 기다린다. 이 함수는 비동기 작업들을 병렬적으로 처리할 때 유용하다.

```dart
Future<List<int>> fetchMultipleData() async {
  List<int> results = await Future.wait([fetchData1(), fetchData2(), fetchData3()]);
  return results;
}
```

2. **Chaining**: `then()`을 통해 여러 `Future` 작업을 순차적으로 연결하여 처리할 수 있다.

```dart
fetchData()
  .then((value) => fetchMoreData(value))
  .then((moreValue) => print("More Data: $moreValue"));
```

#### 수학적 비유

비동기 프로그래밍을 수학적으로 비유하자면, 동기적 함수 호출을 일차 방정식으로 표현할 수 있다. 예를 들어, 함수 $f(x)$는 입력값 $x$에 대해 즉시 값을 반환한다.

$$
f(x) = x + 2
$$

하지만 비동기 함수는 값이 즉시 반환되지 않으며, 미래에 값을 반환할 것을 약속한다. 이를 수학적으로 표현하면, 다음과 같이 `Future`의 개념을 이용하여 비동기적으로 값을 처리할 수 있다.

$$
\text{Future}(f(x)) = \text{Pending}
$$

이때, 함수 $f(x)$의 결과는 값이 준비될 때까지 기다려야 하며, 완료된 이후에는 결과가 반환된다.

{% @mermaid/diagram content="graph LR
X\[x] -->|"f(x)"| Y\[Future Pending]
Y --> Z\[Future Completed]
Z -->|Result| A\[Value]" %}

비동기 함수는 이러한 과정에서 작업이 완료될 때까지 기다리는 것을 보장하며, 결과 값이 준비되면 `Value` 또는 `Error` 상태로 전환된다.

#### 비동기 작업에서의 예외 처리

비동기 함수에서 예외 처리는 일반 함수와 다르다. `Future`가 반환될 때, 예외는 `then()` 또는 `await`을 사용하는 코드에서 비동기적으로 처리해야 한다. Dart에서는 비동기 작업 중 발생한 예외를 처리하기 위해 `try-catch`를 사용할 수 있다.

**try-catch를 통한 예외 처리**

비동기 함수 내부에서 발생하는 예외는 `try-catch` 블록을 사용하여 처리할 수 있다. 일반 함수와 동일한 방식으로 예외를 처리하되, 비동기 함수에서는 `await` 키워드와 함께 예외 처리가 가능한다.

```dart
Future<void> fetchData() async {
  try {
    int data = await getDataFromServer();
    print("Data: $data");
  } catch (e) {
    print("An error occurred: $e");
  }
}
```

위 코드는 서버에서 데이터를 가져오는 비동기 함수에서 발생할 수 있는 예외를 `try-catch`로 처리하는 예시이다. `await`는 비동기 작업이 완료될 때까지 기다리며, 이때 예외가 발생하면 `catch` 블록에서 처리된다.

**catchError를 통한 예외 처리**

`Future` 객체는 `catchError()` 메서드를 사용하여 비동기 작업에서 발생한 예외를 처리할 수 있다. 이는 `then()`과 함께 사용될 때 유용하다.

```dart
getDataFromServer().then((data) {
  print("Data: $data");
}).catchError((error) {
  print("An error occurred: $error");
});
```

이 코드는 `then()`과 `catchError()`를 사용하여 예외를 처리하는 예시이다. 비동기 작업이 성공적으로 완료되면 `then()` 블록이 실행되고, 예외가 발생하면 `catchError()` 블록에서 처리된다.

#### Future의 체인에서의 예외 처리

비동기 작업을 여러 단계로 나누어 체인 형태로 연결할 때, 각 단계에서 예외를 처리할 수 있다. 만약 하나의 `Future`가 실패하면, 해당 오류는 체인 전체로 전파되며, 이후의 작업은 실행되지 않는다.

```dart
fetchData()
  .then((value) => fetchMoreData(value))
  .then((moreValue) => print("More Data: $moreValue"))
  .catchError((error) {
    print("An error occurred: $error");
  });
```

위 예제에서 첫 번째 `fetchData()`가 실패하면, 체인의 나머지 부분은 실행되지 않고 즉시 `catchError()`로 이동하여 오류를 처리한다.

#### 수학적 비유 - 예외 처리

예외 처리의 개념을 수학적으로 설명하자면, 비동기 작업이 진행되면서 예외가 발생할 확률이 존재하는 함수로 비유할 수 있다. 함수 $f(x)$가 입력 $x$에 대해 예외를 발생시킬 가능성이 있는 경우, 해당 함수는 예외를 처리하는 새로운 함수 $g(x)$로 대체될 수 있다.

$$
g(x) = \begin{cases} f(x), & \text{if } f(x) \text{ succeeds} \ \text{Error}, & \text{if } f(x) \text{ fails} \end{cases}
$$

이때, $g(x)$는 성공적으로 값을 반환하거나 예외를 발생시키는 두 가지 상태로 분기할 수 있다.

{% @mermaid/diagram content="graph TD
X\[x] -->|"f(x)"| A\[Future Pending]
A -->|Success| B\[Future Completed with Value]
A -->|Failure| C\[Error Handled]
C --> D\["catchError() 처리"]" %}

이와 같이, 비동기 작업에서는 함수가 정상적으로 완료될 때까지 기다리며, 중간에 발생하는 예외는 `catchError()` 또는 `try-catch`로 처리하여 작업의 실패를 관리할 수 있다.
