← Назад к вопросам

Что такое Future и Stream в Dart? В чём их отличия?

2.0 Middle🔥 251 комментариев
#Dart

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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
}

Главные различия

АспектFutureStream
Количество значенийОдно значение (или ошибка)Множество значений
ПриродаОдноразовое событиеНепрерывный поток событий
ЗавершениеДело завершаетсяМожет работать бесконечно или завершиться
ОтменаМожно использовать CancelTokenМожно отписаться (cancel subscription)
Синтаксисasync/awaitasync* / 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 — для непрерывных потоков данных. Правильный выбор между ними делает код более читаемым и производительным.

Что такое Future и Stream в Dart? В чём их отличия? | PrepBro