Dart에서 비동기 프로그래밍은 아주 중요하다고 할 수 있습니다. Dart에서 비동기 프로그래밍을 작성하는 방법과 동작 방식에 대해서 한번 알아봅시다. 그 전에, 이 글을 읽기전에 이전에 제가 작성한 비동기 프로그래밍 관련 글을 소개합니다. 이 글을 먼저 읽어보신다면 이해하는데 더 큰 도움이 될 것입니다.
[CS]비동기 프로그래밍
일반적으로 우리가 작성하는 대부분의 Dart 코드는 동기식 프로그래밍입니다. 가장 위의 코드부터 아래의 코드를 순차적으로 실행합니다. 당연히 모든 코드는 해당 코드가 종료된 이후에 다음 코드가 동작하게 되겠죠.
void main() {
print("1");
print("2");
print("3");
}
>> 1
>> 2
>> 3
하지만, 경우에 따라서 동기식이 아니라 비동기식 방식으로 코드를 동작해야 되는 경우가 있습니다. 보통 아래의 경우 비동기식 방식을 이용하게 됩니다. 외에도 비동기식 함수들은 모두 비동기 방식입니다.
- 네트워크를 통해 데이터를 가져올 때.
- 데이터 베이스에 쓰기를 수행할 때.
- 파일로부터 데이터를 읽을 때.
위의 경우 모두 비동기식으로 동작하기에 결과가 바로 전달되지는 않습니다. 이 상황에서 동기식으로 접근하게 된다면, 코드는 정상적으로 수행되지 않습니다.
따라서, Dart언어에서 위와 같은 비동기식 방식을 다루기 위해서는 동기식이 아닌 비동기화가 되어야 합니다.
Dart에서는 동기식 방식의 비동기화를 위하여 Future, Async, Await를 사용할 수 있습니다. 비동기 방식으로 수행된 연산의 결과는 Future타입으로 제공되며(만약 결과가 여러 파트로 이루어져 있다면 Stream타입으로 제공), 이러한 비동기식과 상호작용하기 위하여 async, await 키워드를 이용하게 됩니다.
1. future?
future는 Future타입의 인스턴스입니다. 비동기식 함수의 결과를 의미하는데, 완료 혹은 미완료의 상태를 갖고 있습니다.
- 미완료 : 미완료의 future가 반환되며, 함수의 종료 또는 에러 반환을 기다림.
- 완료 : 비동기 함수가 정상적으로 수행된다면 값을 반환하고 그렇지 않으면 에러를 던짐.
보통의 비동기 함수는 Future라는 타입을 함수의 반환타입으로 정의합니다. 그리고 제너릭을 이용하여 해당 비동기 함수의 반환타입을 지정할 수 있습니다. 없다면 void를 이용하면 됩니다.
// 반환타입이 없음.
Future<void> funcA() {
...
}
// int 타입이 반환됨.
Future<int> funcB() {
...
}
2. async, await?
비동기를 다루기 위하여 동기식 방식으로 비동기식으로 바꾸어야할 필요가 있습니다. await 키워드를 이용하면 비동기 함수가 종료될 때까지 기다린 후, 나머지 코드를 수행하게 되고, 이러한 await를 사용하기 위해서는 함수의 선언 시 async 키워드를 삽입해야 합니다.
void func() async {
// 값이 반환되기를 기다림.
final result = await futureIntFunc();
// 1초 후 1출력
print(result);
}
Future<int> futureIntFunc() async {
// 1초간 딜레이 await를 이용하여 1초간 딜레이를 기다림.
await Future.delay(Duration(second: 1));
return 1;
}
만약 func 함수 내부에서 await를 사용하지 않는다면 어떻게 될까요? func함수는 futureIntFunc()함수가 종료되는 것을 기다리지 않기 때문에 함수는 미완료 상태이고, result는
Instance of '_Future<int>'
라는 결과를 갖게 됩니다.
비동기 함수 특히나 네트워킹 도중 여러 예외상황이 발생하게 되고, 이러한 경우 비동기 함수는 정상적으로 종료되지 않기에 에러와 함께 종료됩니다. 아래의 코드를 예로 들어보죠.
Future<void> printOrderMessage() async {
var order = await fetchUserOrder();
print(order);
}
Future<String> fetchUserOrder() async {
await Future.delayed(Duration(seconds: 1));
var ret = "완료";
if (ret == "완료") {
throw "에러 발생";
} else {
return ret;
}
}
void main() async {
// 에러 발생!
await printOrderMessage();
}
이 코드는 임의로 에러를 throw 하도록 제작되었습니다. 이렇게 되면 발생하는 에러를 핸들링할 수 있어야겠죠? try catch문을 이용해서 비동기 함수에서 발생하는 에러를 처리할 수 있습니다.
Future<void> printOrderMessage() async {
try {
print('Awaiting user order...');
var order = await fetchUserOrder();
print(order);
} catch (err) {
print('Caught error: $err');
}
}
Future<String> fetchUserOrder() async {
await Future.delayed(Duration(seconds: 1));
var ret = "완료";
if (ret == "완료") {
throw "에러 발생";
} else {
return ret;
}
}
void main() async {
// 에러 내용이 print됨.
await printOrderMessage();
}