이번 시간에는 FutureBundle이라는 api를 소개하겠습니다. FutureBundle은 여러 api를 호출할 때 비동기 처리를 좀 더 편리하게 해주는 Plugin입니다. 자바스크립트의 promise와 코루틴(코틀린)의 join과 유사한 방식으로 코드를 구현할 수 있습니다.
FutureBundle을 사용한 경우와 아닌 경우의 예시를 들면서 설명하겠습니다.
https://pub.dev/packages/future_bundle
void main() async{
Stopwatch stopwatch = Stopwatch()..start();
print('시작');
await callApi("첫번째");
await callApi("두번째");
await callApi("세번째");
await callApi("네번째");
await callApi("다섯번째");
print('호출완료, 걸린 시간: ${stopwatch.elapsed}');
}
Future<void> callApi(String msg) async{
await Future.delayed(Duration(milliseconds: 1000));
print('msg : $msg');
}
Console
시작
msg : 첫번째
msg : 두번째
msg : 세번째
msg : 네번째
msg : 다섯번째
호출완료, 걸린 시간: 0:00:05.055599
이럴 경우 5개의 api호출이 완료되는데 5초의 시간이 걸렸습니다.
코드 보기가 편리하고, 시작과 완료가 명확합니다. 하지만 시간이 오래걸립니다.
시간 단축을 위해서 두번째 방법으로 넘어가보겠습니다.
void main() async{
Stopwatch stopwatch = Stopwatch()..start();
print('시작');
callApi("첫번째");
callApi("두번째");
callApi("세번째");
callApi("네번째");
callApi("다섯번째");
print('호출완료, 걸린 시간: ${stopwatch.elapsed}');
}
Future<void> callApi(String msg) async{
Future.delayed(Duration(milliseconds: 1000)).then((value){
print('msg : $msg');
});
}
Console
시작
호출완료, 걸린 시간: 0:00:00.001300
msg : 첫번째
msg : 두번째
msg : 세번째
msg : 네번째
msg : 다섯번째
호출 완료 print를 마지막에 호출했지만 api를 동기로 처리했기 때문에(Future/await을 사용하지 않고, Future/then을 사용) 호출완료 프린트가 두번째로 출력됐습니다.
stopwatch로 찍히지는 않았지만 아마도 1초만에 5개의 api가 모두 완료되었을 것입니다.
그런데 만약에 5개의 api 호출이 모두 완료된 후에 print를 찍고 싶다면 어떻게 할까요? 세번째로 넘어가겠습니다.
Stopwatch stopwatch = Stopwatch();
int count = 0;
void main() async{
stopwatch.start();
print('시작');
callApi("첫번째", 1000);
callApi("두번째", 2000);
callApi("세번째", 1500);
callApi("네번째", 500);
callApi("다섯번째", 3000);
}
Future<void> callApi(String msg, int time) async{
Future.delayed(Duration(milliseconds: time)).then((value){
print('msg : $msg');
counting();
});
}
void counting(){
count++;
if(count == 5){
print('호출완료, 걸린 시간: ${stopwatch.elapsed}');
}
}
Console
시작
msg : 네번째
msg : 첫번째
msg : 세번째
msg : 두번째
msg : 다섯번째
호출완료, 걸린 시간: 0:00:03.019600
두번째와 달라진 부분이 몇 가지 있습니다. 먼저 Stopwatch와 count 변수를 전역으로 선언했습니다. callApi 함수에 int 값을 넘겨서 api 호출 시간을 다르게 했습니다. 그리고 api 호출이 완료됐을 때마다 counting 함수를 호출해서 count를 증가시켰고, count가 5가 됐을 때 호출 완료 프린트를 출력했습니다.
입력된 시간이 적은 순서대로 print가 출력되었고, 5개의 api가 완료됐을 때 호출완료 print가 출력됐습니다.
여기서 한번 더 조건을 설정해보겠습니다.
main() 함수에서 실행되는 5개의 api를 동기 처리하면서(Future/await) 시간은 비동기 호출과 같게 하고 싶다면?
이러한 코드를 구축하면 꽤 많은 노력이 필요할 것입니다. 이때 future_bundle plugin을 사용하면 손쉽게 구축할 수 있습니다.
...
ElevatedButton(
onPressed: () async {
Stopwatch stopwatch = Stopwatch()..start();
print('시작');
await FutureBundle().pack(futures: [
callApi("첫번째", 1000),
callApi("두번째", 2000),
callApi("세번째", 1500),
callApi("네번째", 500),
callApi("다섯번째", 3000),
]);
print('호출완료, 걸린 시간: ${stopwatch.elapsed}');
},
child: Text("future_bundle"))
...
Future<void> callApi(String msg, int time) async {
await Future.delayed(Duration(milliseconds: time));
print('msg : $msg');
}
Console
I/flutter (19158): 시작
I/flutter (19158): msg : 네번째
I/flutter (19158): msg : 첫번째
I/flutter (19158): msg : 세번째
I/flutter (19158): msg : 두번째
I/flutter (19158): msg : 다섯번째
[log] FutureBundle pack duration milliseconds : 3018
I/flutter (19158): 호출완료, 걸린 시간: 0:00:03.022277
Console을 보면 호출 시간이 빠른 순서대로 print가 출력되었습니다. 그리고 호출완료 프린트가 제일 마지막에 출력된 것을 보니 5개의 api를 비동기 방식 호출 시간과 동일하게 가져가면서 동기 방식 처럼 묶어서 api를 호출할 수 있습니다. 따로 count를 체크할 필요 없이(에러 요소를 줄이면서) 비동기처리/네트워크 통신을 할 수 있습니다.
그런데 FutureBundle 플러그인의 진짜 장점은 return 값이 있는 경우입니다. 모든 Future 함수가 return하는 값들을 한번에 모아서 받을 수도 있고, 각각의 함수가 완료됐을 때 바로바로 return 값을 받아볼 수도 있습니다.
...
ElevatedButton(
onPressed: () async {
Stopwatch stopwatch = Stopwatch()..start();
print('시작');
var result = await FutureBundle().pack(
futures: [
callApi("첫번째", 1000),
callApi("두번째", 2000),
callApi("세번째", 1500),
callApi("네번째", 500),
callApi("다섯번째", 3000),
],
data: (data) {
print('data : $data');
});
print('호출완료, 걸린 시간: ${stopwatch.elapsed}');
print('result : $result');
},
child: Text("future_bundle"))
...
Future<String> callApi(String msg, int time) async {
await Future.delayed(Duration(milliseconds: time));
return msg;
}
Console
I/flutter (19158): 시작
I/flutter (19158): data : {3: 네번째}
I/flutter (19158): data : {0: 첫번째}
I/flutter (19158): data : {2: 세번째}
I/flutter (19158): data : {1: 두번째}
I/flutter (19158): data : {4: 다섯번째}
I/flutter (19158): 호출완료, 걸린 시간: 0:00:03.013022
[log] FutureBundle pack duration milliseconds : 3010
I/flutter (19158): result : [첫번째, 두번째, 세번째, 네번째, 다섯번째]
코드를 보면 data callback 함수가 맵 타입으로 데이터를 반환하고 있습니다. callApi("네번째", 500)는 네번째로 입력된 api이지만 시간이 가장 짧기 때문에 제일 먼저 "data : {3: 네번째}" 값을 반환했습니다. key인 3은 입력한 순서(index)이고, value인 "네번째"는 callApi() 함수의 return 값입니다.
result 변수에는 api들을 입력한 순서대로 return값이 배열로 들어가 있습니다.
이처럼 비동기 처리를 동기 방식으로 처리할 수 있으며, return값들을 손쉽게 받아볼 수 있습니다.
아래는 FutureBundle의 비동기 방식 호출입니다.
...
Stopwatch stopwatch = Stopwatch()..start();
print('시작');
FutureBundle().pack(
futures: [
callApi("첫번째", 1000),
callApi("두번째", 2000),
callApi("세번째", 1500),
callApi("네번째", 500),
callApi("다섯번째", 3000),
],
data: (data) {
print('data : $data');
},
complete: (results) {
print('results : $results, 걸린 시간: ${stopwatch.elapsed}');
},
);
print('호출완료, 걸린 시간: ${stopwatch.elapsed}');
...
Future<String> callApi(String msg, int time) async {
await Future.delayed(Duration(milliseconds: time));
return msg;
}
Console
I/flutter (19158): 시작
I/flutter (19158): 호출완료, 걸린 시간: 0:00:00.001518
I/flutter (19158): data : {3: 네번째}
I/flutter (19158): data : {0: 첫번째}
I/flutter (19158): data : {2: 세번째}
I/flutter (19158): data : {1: 두번째}
I/flutter (19158): data : {4: 다섯번째}
I/flutter (19158): results : [첫번째, 두번째, 세번째, 네번째, 다섯번째], 걸린 시간: 0:00:03.005583
비동기 방식으로 호출했기 때문에 호출완료 프린트가 시작 다음에 출력됐습니다. 하지만 complete함수를 통해 results 값들이 반환된 로그를 보면 걸린 시간은 3초인 것을 확인할 수 있습니다.