Что такое Future и Stream в Dart? В чём их отличия?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Future и Stream в Dart: Полное руководство
Future и Stream — это два основных способа работы с асинхронным кодом в Dart. Они решают разные задачи и используются в зависимости от характера данных.
Future — одно значение в будущем
Future представляет результат асинхронной операции, которая произойдёт в будущем (или не произойдёт, если произойдёт ошибка). Это как обещание вернуть ровно одно значение.
// Простой Future
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return "Hello, Future!";
}
// Использование
void main() async {
final data = await fetchData();
print(data); // Hello, Future!
}
// Альтернативный способ без async/await
void main() {
fetchData().then((data) {
print(data); // Hello, Future!
}).catchError((error) {
print("Error: $error");
});
}
Состояния Future:
Future<int> calculate() async {
// Состояние: Pending (ожидание)
await Future.delayed(Duration(seconds: 1));
// Состояние: Fulfilled (успешно выполнено)
return 42;
// Или Rejected (ошибка)
// throw Exception("Something went wrong");
}
void main() async {
print("Start"); // Печатается сразу
final result = await calculate();
print("Result: $result"); // Печатается через 1 сек
}
Stream — множество значений со временем
Stream — это поток данных, который может испускать множество значений со временем, заканчиваясь успешно или с ошибкой. Это как бесконечный канал для передачи данных.
// Простой Stream
Stream<int> countUpTo(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
yield i; // Испускаем значение
}
}
// Использование
void main() {
countUpTo(5).listen((value) {
print("Value: $value");
});
// Output:
// Value: 1
// Value: 2
// Value: 3
// Value: 4
// Value: 5
}
Stream с обработкой ошибок:
Stream<int> unreliableStream() async* {
yield 1;
yield 2;
throw Exception("Something went wrong");
yield 3; // Не будет выполнено
}
void main() {
unreliableStream().listen(
(value) => print("Value: $value"),
onError: (error) => print("Error: $error"),
onDone: () => print("Stream finished"),
);
// Output:
// Value: 1
// Value: 2
// Error: Exception: Something went wrong
}
Главные различия
| Аспект | Future | Stream |
|---|---|---|
| Количество значений | Одно значение (или ошибка) | Множество значений |
| Природа | Одноразовое событие | Непрерывный поток событий |
| Завершение | Дело завершается | Может работать бесконечно или завершиться |
| Отмена | Можно использовать CancelToken | Можно отписаться (cancel subscription) |
| Синтаксис | async/await | async* / yield или StreamController |
| Метаморфозы | .then(), .catchError() | .listen(), .where(), .map() |
| Кэширование | Future автоматически кэшируется | Stream передаёт каждый слушателю |
| Примеры | Запрос к API, чтение файла | Нажатия кнопки, данные GPS, WebSocket |
Практические примеры во Flutter
Future — для одноразовых операций:
class UserRepository {
Future<User> getUser(String id) async {
final response = await http.get(Uri.parse("/api/users/$id"));
return User.fromJson(json.decode(response.body));
}
}
class UserScreen extends StatefulWidget {
@override
State<UserScreen> createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> {
late Future<User> _userFuture;
@override
void initState() {
super.initState();
_userFuture = UserRepository().getUser("123");
}
@override
Widget build(BuildContext context) {
return FutureBuilder<User>(
future: _userFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
} else {
return Text("User: ${snapshot.data!.name}");
}
},
);
}
}
Stream — для непрерывных данных:
class LocationService {
Stream<Location> getCurrentLocation() {
return Geolocator.getPositionStream().map(
(position) => Location(
latitude: position.latitude,
longitude: position.longitude,
),
);
}
}
class MapScreen extends StatefulWidget {
@override
State<MapScreen> createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
late StreamSubscription<Location> _locationSubscription;
Location? _currentLocation;
@override
void initState() {
super.initState();
_locationSubscription = LocationService()
.getCurrentLocation()
.listen((location) {
setState(() => _currentLocation = location);
});
}
@override
void dispose() {
_locationSubscription.cancel(); // Важно отписаться!
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder<Location>(
stream: LocationService().getCurrentLocation(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text("Waiting for location...");
}
final location = snapshot.data!;
return Text("Lat: ${location.latitude}, Lng: ${location.longitude}");
},
);
}
}
Трансформация Stream
Stream<int> numbers() async* {
for (int i = 1; i <= 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
void main() {
numbers()
.where((n) => n.isEven) // Фильтр: только чётные
.map((n) => n * 2) // Преобразование: умножить на 2
.take(2) // Только первые 2
.listen((value) => print(value));
// Output:
// 4 (2 * 2)
// 8 (4 * 2)
}
StreamController для более сложных сценариев
class EventBus {
final _controller = StreamController<Event>.broadcast();
Stream<Event> get events => _controller.stream;
void emit(Event event) {
_controller.add(event);
}
void dispose() {
_controller.close();
}
}
// Использование
class MyService {
final _eventBus = EventBus();
Stream<Event> get events => _eventBus.events;
Future<void> doSomething() async {
await someAsyncOperation();
_eventBus.emit(SuccessEvent());
}
}
Когда использовать что
Используй Future когда:
- Нужно получить один результат (ответ от сервера, результат вычисления)
- Операция выполняется один раз
- Нужна простая обработка ошибок
Используй Stream когда:
- Нужно обрабатывать множество значений
- События приходят со временем (пользовательский ввод, сенсоры)
- Нужна подписка на изменения
- Нужно фильтровать/трансформировать данные
Заключение
Future и Stream — это мощные инструменты для асинхронного программирования в Dart. Future идеален для одноразовых операций, а Stream — для непрерывных потоков данных. Правильный выбор между ними делает код более читаемым и производительным.