Параллелизм во Flutter и Dart - одновременное выполнение нескольких асинхронных запросов. Как правильно?
Из АПИ хочу получить данные, но при этом в описании к АПИ сказано, что нужно получать данные одновременно сразу из нескольких таблиц и уже из запроса getStops, получить финальный результат.
То есть есть три списка
List<RaceCard> list = [];
List<Stop> stop = [];
List<ScheduleVariants> sheduler = [];
и на каждый приходится по своему запросу к АПИ. Сейчас реализовываю это так, но в ответ получаю пустую страницу:
class Direction extends StatefulWidget {
final int mvId;
final int mrId;
final int stId;
const Direction({Key key, this.mrId, this.mvId, this.stId}) : super(key: key);
@override
_DirectionState createState() => _DirectionState();
}
class _DirectionState extends State<Direction> {
@override
void initState() {
_getData();
super.initState();
}
List<RaceCard> list = [];
List<Stop> stop = [];
List<ScheduleVariants> sheduler = [];
Future _getData() async {
String basicAuth =
'Basic ' + base64Encode(utf8.encode('$username:$password'));
print(basicAuth);
final results = await Future.wait([
http.get(
Uri.parse(
: basicAuth}),
http.get(
Uri.parse(
),
headers: <String, String>{'authorization': basicAuth}),
http.get(
Uri.parse(
"),
headers: <String, String>{'authorization': basicAuth})
]);
setState(() {
list = json.decode(results[0].body);
stop = json.decode(results[1].body);
sheduler = json.decode(results[2].body);
sheduler.where((element) => element.mrId == widget.mrId).toList();
stop.where((element) => element.stId == widget.stId).toList();
list.where((element) => element.stId == widget.stId).toList();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: FutureBuilder(
future: _getData(),
builder: (context, projectSnap) {
if (projectSnap.connectionState == ConnectionState.none &&
projectSnap.hasData == null) {
return Container();
}
return ListView.builder(itemCount: stop.length, itemBuilder: (context, index){
return ListTile(
title: Text(stop[index].stTitle),
);
});
},),
);
}
}
В стэктрейсе такая ошибка:
E/flutter (18748): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: FormatException: Unexpected end of input (at character 1)
E/flutter (18748):
E/flutter (18748): ^
E/flutter (18748):
E/flutter (18748): #0 _ChunkedJsonParser.fail (dart:convert-patch/convert_patch.dart:1404:5)
E/flutter (18748): #1 _ChunkedJsonParser.close (dart:convert-patch/convert_patch.dart:522:7)
E/flutter (18748): #2 _parseJson (dart:convert-patch/convert_patch.dart:41:10)
E/flutter (18748): #3 JsonDecoder.convert (dart:convert/json.dart:506:36)
E/flutter (18748): #4 JsonCodec.decode (dart:convert/json.dart:157:41)
E/flutter (18748): #5 _DirectionState._getData.<anonymous closure> (package:flutter_app/main.dart:214:17)
E/flutter (18748): #6 State.setState (package:flutter/src/widgets/framework.dart:1267:30)
E/flutter (18748): #7 _DirectionState._getData (package:flutter_app/main.dart:213:3)
E/flutter (18748): <asynchronous suspension>
E/flutter (18748):
Ответы (1 шт):
одновременное выполнение нескольких асинхронных запросов. Как правильно?
"Правильно" - никак, потому что каких-то единых и однозначных правил не существует...
А относительно удобно - можно таким образом:
В классах моделей описываем фабричный конструктор
fromJson, для разбора JSON-мапы единичной сущности в новый экземпляр объекта (или используем генерацию кода пакетом json_serializable)Пишем какую-нибудь вспомогательную функцию запроса списка сущностей, для сокращения повторений кода
Типа такой функции (код очень базовый и написан на ходу - не рекомендую его копипастить):Future<List<T>> fetchList<T>(String endpoint, { Map<String, String>? query, Map<String, String>? headers, required T Function(dynamic) itemFactory, }) async { final uri = Uri.https(apiHost, endpoint, query); final Map<String, String> mergedHeaders = (headers is Map) ? ({}..addAll(baseHeaders)..addAll(headers!)) : baseHeaders; final response = await http.get(uri, headers: mergedHeaders); if (response.statusCode != 200) throw Exception('Error[${response.statusCode}] on GET ${uri.toString()}'); return List<T>.from(jsonDecode(response.body).map<T>(itemFactory)); } // предполагается что `apiHost` и `baseHeaders` объявлены в области видимостиСобственно, используем первые две вещи, и радуемся
Например, в контексте этого вопроса, можно представить примерно такой код:try { // параллельные запросы с разбором полученных данных в List'ы final results = await Future.wait([ fetchList<RaceCard>('getRaceCards.php', query: { 'fmt': 'json', 'mv_id': mv_id }, itemFactory: (json) => RaceCard.fromJson(json), ), fetchList<Stop>('getStops.php', query: { 'fmt': 'json' }, itemFactory: (json) => Stop.fromJson(json), ), fetchList<ScheduleVariants>('getMarshVariants.php', query: { 'fmt': 'json' }, itemFactory: (json) => ScheduleVariants.fromJson(json), ), ], eagerError: true); // если выполнение дошло досюда, значит ошибок не возникло - пробуем изменить стейт setState(() { list = results[0] as List<RaceCard>; stops = results[1] as List<Stop>; shedulers = results[2] as List<ScheduleVariants>; /* .... */ }); } catch (err) { // тут какие-то действия при любой ошибке: выполнения одного из запросов, разбора данных в объект, изменения стейта, и т.д. }
В чем разница с кодом в вопросе:
- в том что код не придется дублировать в каждом классе стейта
(т.к. классы модели сами отвечают за свое инстанцирование из JSON... а функцию запроса списка можно сделать методом суперкласса расширяющего либоState, либо базовый класс API-провайдера, и наследовать его по необходимости) - в раннем отлове ошибок алгоритма, благодаря типизации полей модельных классов
(к этому естественным образом обяжет код фабричного конструктораfromJson) - в непадающем от каждого чиха приложении, и более удобной отладке.
Ну а ошибка FormatException: Unexpected end of input (at character 1) возникает при вызове json.decode: из-за попытки JSON-разбора строки которая не является валидным JSON (как я уже написал в комменте). Это проблема с данными, проблема содержимого ответа какого-то отдельного сервера в каком-то частном случае... к заданному общему вопросу, эта частная проблема по сути не относится (об адекватном/безопасном разборе конкретного серверного ответа можно задать отдельный вопрос, прикрепив к нему содержимое того что сервер присылает).
Принцип общей обработки ошибок (при любом из запросов) приведен в примере выше, в последнем блоке кода.
Такой подход используется когда принципиально не важно, какой из запросов повалится с ошибкой - чтобы, например, показать пользователю простое понятное уведомление не перегруженное "пугающими техническими подробностями".
Если же нужна индивидуальная обработка ошибок каждого из запросов в "пачке" - то это уже отдельная, и весьма обширная тема.
Такое можно сделать множеством разных способов: и через собственные классы исключений, и через коллбэки, и через переменную в состоянии, и через свой класс объекта запроса, и резолвом констант ошибок, и так далее...